# Arduino Time library
發現原來此函式庫不一定要用到 RTC, 它只是提供一堆跟時間有關的函式方便我們處理日期時間而已, 我們可以透過 setTime() 函式讓 Arduino 與外部時鐘同步, 而 RTC 只不過是同步來源的一種而已, 也可以透過網路與 NTP 伺服器同步 :
"The Time library adds timekeeping functionality to Arduino with or without external timekeeping hardware."
可從 Github 下載 Time 函式庫 :
# https://github.com/PaulStoffregen/Time
把下載下來的 Time-master.zip 檔解壓縮後放到 Arduino IDE 的安裝目錄的 libraries 資料夾下面, 重開 IDE 就會抓到此函式庫了. 在 Arduino Time library 底下有一個範例程式, 我把範例程式整理並加註中文說明如下 :
測試 1 :
#include <Time.h>
#define TIME_MSG_LEN 11 //時間字串長度含標頭 T 為 11 字元 (T+10位數之 Unix 時戳)
#define TIME_HEADER 'T' //時間同步訊息標頭
#define TIME_REQUEST 7 //ASCII bell character requests a time sync message
//時間字串格式 T1262347200 //noon Jan 1 2010
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available()) { //串列埠收到訊息
processSyncMessage(); //處理時間同步字串
}
if (timeStatus() == timeNotSet) { //時間尚未同步
Serial.println("waiting for sync message");
}
else { //時間已同步, 顯示時間
digitalClockDisplay();
}
delay(1000);
}
void digitalClockDisplay() { //顯示時間
// digital clock display of the time
Serial.print(hour()); //時
printDigits(minute()); //分
printDigits(second()); //秒
Serial.print(" ");
Serial.print(day()); //日
Serial.print(" ");
Serial.print(month()); //月
Serial.print(" ");
Serial.print(year()); //年
Serial.println();
}
void printDigits(int digits) { //顯示數值 & 位數調整
// utility function for digital clock display: prints preceding colon and leading 0
Serial.print(":");
if (digits < 10) {Serial.print('0');}
Serial.print(digits);
}
void processSyncMessage() { //處理同步時間字串
//若自串列埠收到同步時間訊息, 更新時間並回傳 true
while (Serial.available() >= TIME_MSG_LEN ) { //收到至少 11 字元的同步訊息
char c=Serial.read();
Serial.print(c);
if (c==TIME_HEADER ) { //收到同步訊息標頭 'T'
time_t pctime=0; //定義時間變數初值 (整數)
for (int i=0; i<TIME_MSG_LEN-1; i++) { //讀取 Unix 時戳字串
c=Serial.read();
if (c >= '0' && c <= '9') { //必須是 0~9 數字
pctime=(10 * pctime) + (c - '0') ; //將數字字元轉成整數
}
}
setTime(pctime); //將 Arduino 內部時鐘與自串列埠收到的時間訊息同步
}
}
}
上傳執行後, 序列埠監控視窗會不斷出現 "waiting for sync message", 輸入 T1262347200 按傳送就會將 Arduino 內部時鐘與此時間訊息同步, 序列埠監控視窗顯示如下 :
waiting for sync message
waiting for sync message
waiting for sync message
T12:00:00 1 1 2010
12:00:01 1 1 2010
12:00:02 1 1 2010
12:00:03 1 1 2010
12:00:04 1 1 2010
12:00:05 1 1 2010
12:00:06 1 1 2010
接下來我想修改上回 NTP 對時程式來使 Arduino 內部時鐘與其同步, 參考 :
# 以 Arduino + ESP8266 物聯網模組重作 NTP 實驗 (三)
我參考上面範例以及之前我做的 NTP 實驗, 希望從 NTP 取得 GMT 的 Unix 時間後, 加上 8 小時 (28800 秒) 得到台灣時間的 Unix 秒數, 再傳給 Time 函式庫的 setTime() 函數來設定 Arduino 的系統時鐘, 這樣就能透過此函式庫來取得完整的日期與時間訊息了, 如下面測試 2 所示 :
測試 2 :
#include <SoftwareSerial.h>
#define DEBUG true
#include <Time.h>
SoftwareSerial esp8266(7,8); //(RX,TX)
byte packetBuffer[128]; //buffer : send & recv data to/from NTP
int i=0; //sync cycle counter
void setup() {
Serial.begin(9600);
esp8266.begin(9600);
sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
sendData("AT+GMR\r\n",1000,DEBUG);
delay(3000); //wait for wifi connection to get local ip
sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
Serial.println("Sending request to NTP server ...");
setTime(getTime());
}
void loop() {
showDateTime();
++i;
if (i >= 60) { //1 分鐘同步週期到了
setTime(getTime()); //設定 Arduino 系統時鐘
i=0;
}
else {delay(1000);} //一秒跳一次
}
void showDateTime() { //輸出如 2016-07-15 19:56:12 格式的日期時間
Serial.print(year());
Serial.print("-");
byte M=month();
if (M < 10) {Serial.print('0');}
Serial.print(M);
Serial.print("-");
byte d=day();
if (d < 10) {Serial.print('0');}
Serial.print(d);
Serial.print(" ");
byte h=hour();
if (h < 10) {Serial.print('0');}
Serial.print(h);
Serial.print(":");
byte m=minute();
if (m < 10) {Serial.print('0');}
Serial.print(m);
Serial.print(":");
byte s=second();
if (s < 10) {Serial.print('0');}
Serial.println(s);
}
long getTime() { //從 NTP 伺服器取得 Unix 時間秒數
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n",5000,DEBUG);
memset(packetBuffer,0,128); //clear buffer
packetBuffer[0]=0xE3; //LI, Version, Mode
packetBuffer[1]=0x00; //Stratum, or type of clock
packetBuffer[2]=0x06; //Polling Interval
packetBuffer[3]=0xEC; //Peer Clock Precision
packetBuffer[12]=0x31; //reference ID (4 bytes)
packetBuffer[13]=0x4E;
packetBuffer[14]=0x31;
packetBuffer[15]=0x34;
//send request to NTP Server
sendData("AT+CIPSEND=48\r\n",1000,DEBUG); //send data length
for (byte i=0; i < 48; i++) {
esp8266.write(packetBuffer[i]);
delay(5);
}
//deal with NTP response
memset(packetBuffer,0,128); //clear buffer to store NTP response
Serial.println();
Serial.println("NTP server answered : ");
int i=0; //packet byte counter
while (esp8266.available() > 0) { //if receive NTP response : fall in loop
byte ch=esp8266.read(); //got NTP response, read one byte for each loop
if (i < 128) {packetBuffer[i]=ch;} //store received byte to packet buffer
//show receving bytes in hex
if (ch < 0x10) {Serial.print('0');} //prefix with '0' if byte value 0~9
Serial.print(ch, HEX);
Serial.print(' ');
if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
delay(5); //wait 5ms for next incoming byte
i++; //increment packet byte counter
if ((i < 104) && (esp8266.available() == 0)) { //wainting if lags
//Response packets not enough but no response : wait 1.5 seconds
byte wcount=0; //waiting counter
while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
Serial.print("!"); //show ! means waiting for response packet
delay(100);
wcount += 1; //increment waiting counter
if (wcount >= 15) {break;} //waiting timeout : quit loop
}
}
}
Serial.println();
Serial.println();
Serial.print(i+1);
Serial.println(" bytes received"); // will be more than 48
//Show time stamp (locates from byte 101~104 of the response packet)
Serial.print("NTP time stamp packets (byte 101~104)=");
Serial.print(packetBuffer[101],HEX);
Serial.print(" ");
Serial.print(packetBuffer[102],HEX);
Serial.print(" ");
Serial.print(packetBuffer[103],HEX);
Serial.print(" ");
Serial.print(packetBuffer[104],HEX);
Serial.println();
//handling time packets (4 bytes long) : combine them into words
unsigned long highWord=word(packetBuffer[101],packetBuffer[102]);
unsigned long lowWord=word(packetBuffer[103],packetBuffer[104]);
//shift high word 16 bits left & OR with low word to form a double word
//the result is a NTP time stamp (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("NTP time stamp (seconds since 1900-01-01)=");
Serial.println(secsSince1900);
//convert NTP time stamp to Unix time stamp :
//Unix time starts on Jan 1 1970=2208988800 seconds since 1900-01-01
//subtract seventy years to get Unix time stamp, plus 28800 (UTC+8)
unsigned long epoch=secsSince1900 - 2208988800UL;
Serial.print("Unix time stamp (seconds since 1970-01-01)=");
Serial.println(epoch); //print Unix time
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
return epoch + 28800; //台灣時間為 GMT+8 小時, 即 28800 秒
}
String sendData(String command, const int timeout, boolean debug) {
String response="";
esp8266.print(command); // send the read character to the esp8266
long int time=millis();
while ((time+timeout) > millis()) {
while(esp8266.available()) {
// The esp has data so display its output to the serial window
char c=esp8266.read(); // read the next character.
response += c;
}
}
if (debug) {Serial.print(response);}
return response;
}
在 setup() 函式中, 我們先呼叫自訂的 getTime() 函數從 NTP 伺服器取得台灣時區的 Unix 秒數, 再將其傳入 Time 函式庫的 setTime() 函數來設定 Arduino 內部時鐘. 而在 loop() 迴圈裡, 先呼叫自訂的 showDateTime() 函數來顯示目前的日期時間, 然後增量同步計數器 i, 若到達 60 次時就呼叫 getTime() 來與 NTP 伺服器對時, 並且將計數器歸零.
程式上傳後擷取串列埠監控視窗訊息如下 :
AT+RST
OK
bB�鑭b禔 ��"兀\�侒��餾�
[System Ready, Vendor:www.ai-thinker.com]
AT+GMR
0018000902
OK
AT+CIFSR
192.168.2.108
OK
Sending request to NTP server ...
AT+CIPSTART="UDP","91.226.136.136",123
OK
AT+CIPSEND=48
>
NTP server answered :
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A !!!0D
0A 2B 49 50 44 2C 34 38 3A 24
02 06 ED 00 00 00 22 00 00 02
44 6B DC 0B 87 DB 33 43 02 94
20 CF 9D 00 00 00 00 00 00 00
00 DB 33 44 59 E4 D7 13 48 DB
33 44 59 E4 D7 9F D7 0D 0A 4F
4B 0D
123 bytes received
NTP time stamp packets (byte 101~104)=DB 33 44 59
NTP time stamp (seconds since 1900-01-01)=3677570137
Unix time stamp (seconds since 1970-01-01)=1468581337
AT+CIPCLOSE
OK
Unlink
2016-07-15 19:15:37
2016-07-15 19:15:38
2016-07-15 19:15:39
2016-07-15 19:15:40
2016-07-15 19:15:41
2016-07-15 19:15:42
2016-07-15 19:15:43
... (每秒跳一次)
2016-07-15 19:16:29
2016-07-15 19:16:30
2016-07-15 19:16:31
2016-07-15 19:16:32
2016-07-15 19:16:33
2016-07-15 19:16:34
2016-07-15 19:16:35
2016-07-15 19:16:36
AT+CIPSTART="UDP","91.226.136.136",123
OK
AT+CIPSEND=48
>
NTP server answered :
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A !!0D
0A 2B 49 50 44 2C 34 38 3A 24
02 06 ED 00 00 00 22 00 00 02
86 6B DC 0B 87 DB 33 43 02 94
20 CF 9D 00 00 00 00 00 00 00
00 DB 33 44 9D 64 A8 39 88 DB
33 44 9D 64 B2 E4 33 0D 0A 4F
4B 0D 0A
124 bytes received
NTP time stamp packets (byte 101~104)=DB 33 44 9D
NTP time stamp (seconds since 1900-01-01)=3677570205
Unix time stamp (seconds since 1970-01-01)=1468581405
AT+CIPCLOSE
OK
Unlink
2016-07-15 19:16:45
2016-07-15 19:16:46
2016-07-15 19:16:47
2016-07-15 19:16:48
2016-07-15 19:16:49
2016-07-15 19:16:50
...
可見每次同步大約花掉 8 秒時間.
上面測試二使用一個同步計數器變數 i 來計算是否該與 NTP 伺服器同步了, 其實這可以用 TimeAlarms 函式庫來做, 同樣從 Github 的 Clone or download 下載 :
# https://github.com/PaulStoffregen/TimeAlarms
解壓縮 TimeAlarms-master.zip 至 Arduino IDE 安裝目錄的 libraries 下即可. 程式如下 :
測試 3 :
#include <SoftwareSerial.h>
#define DEBUG true
#include <Time.h>
#include <TimeAlarms.h>
SoftwareSerial esp8266(7,8); //(RX,TX)
byte packetBuffer[128]; //buffer : send & recv data to/from NTP
void setup() {
Serial.begin(9600);
esp8266.begin(9600);
sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
sendData("AT+GMR\r\n",1000,DEBUG);
delay(3000); //wait for wifi connection to get local ip
sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
Serial.println("Sending request to NTP server ...");
sync_clock();
Alarm.timerRepeat(60, sync_clock); //設定每 60 秒觸發執行 sync_clock()
}
void loop() {
showDateTime();
Alarm.delay(1000); //每一秒檢查一下觸發條件
}
void sync_clock() {
setTime(getTime());
}
void showDateTime() {
Serial.print(year());
Serial.print("-");
byte M=month();
if (M < 10) {Serial.print('0');}
Serial.print(M);
Serial.print("-");
byte d=day();
if (d < 10) {Serial.print('0');}
Serial.print(d);
Serial.print(" ");
byte h=hour();
if (h < 10) {Serial.print('0');}
Serial.print(h);
Serial.print(":");
byte m=minute();
if (m < 10) {Serial.print('0');}
Serial.print(m);
Serial.print(":");
byte s=second();
if (s < 10) {Serial.print('0');}
Serial.println(s);
}
long getTime() {
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n",5000,DEBUG);
memset(packetBuffer,0,128); //clear buffer
packetBuffer[0]=0xE3; //LI, Version, Mode
packetBuffer[1]=0x00; //Stratum, or type of clock
packetBuffer[2]=0x06; //Polling Interval
packetBuffer[3]=0xEC; //Peer Clock Precision
packetBuffer[12]=0x31; //reference ID (4 bytes)
packetBuffer[13]=0x4E;
packetBuffer[14]=0x31;
packetBuffer[15]=0x34;
//send request to NTP Server
sendData("AT+CIPSEND=48\r\n",1000,DEBUG); //send data length
for (byte i=0; i < 48; i++) {
esp8266.write(packetBuffer[i]);
delay(5);
}
//deal with NTP response
memset(packetBuffer,0,128); //clear buffer to store NTP response
Serial.println();
Serial.println("NTP server answered : ");
int i=0; //packet byte counter
while (esp8266.available() > 0) { //if receive NTP response : fall in loop
byte ch=esp8266.read(); //got NTP response, read one byte for each loop
if (i < 128) {packetBuffer[i]=ch;} //store received byte to packet buffer
//show receving bytes in hex
if (ch < 0x10) {Serial.print('0');} //prefix with '0' if byte value 0~9
Serial.print(ch, HEX);
Serial.print(' ');
if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
delay(5); //wait 5ms for next incoming byte
i++; //increment packet byte counter
if ((i < 104) && (esp8266.available() == 0)) { //wainting if lags
//Response packets not enough but no response : wait 1.5 seconds
byte wcount=0; //waiting counter
while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
Serial.print("!"); //show ! means waiting for response packet
delay(100);
wcount += 1; //increment waiting counter
if (wcount >= 15) {break;} //waiting timeout : quit loop
}
}
}
Serial.println();
Serial.println();
Serial.print(i+1);
Serial.println(" bytes received"); // will be more than 48
//Show time stamp (locates from byte 101~104 of the response packet)
Serial.print("NTP time stamp packets (byte 101~104)=");
Serial.print(packetBuffer[101],HEX);
Serial.print(" ");
Serial.print(packetBuffer[102],HEX);
Serial.print(" ");
Serial.print(packetBuffer[103],HEX);
Serial.print(" ");
Serial.print(packetBuffer[104],HEX);
Serial.println();
//handling time packets (4 bytes long) : combine them into words
unsigned long highWord=word(packetBuffer[101],packetBuffer[102]);
unsigned long lowWord=word(packetBuffer[103],packetBuffer[104]);
//shift high word 16 bits left & OR with low word to form a double word
//the result is a NTP time stamp (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("NTP time stamp (seconds since 1900-01-01)=");
Serial.println(secsSince1900);
//convert NTP time stamp to Unix time stamp :
//Unix time starts on Jan 1 1970=2208988800 seconds since 1900-01-01
//subtract seventy years to get Unix time stamp, plus 28800 (UTC+8)
unsigned long epoch=secsSince1900 - 2208988800UL;
Serial.print("Unix time stamp (seconds since 1970-01-01)=");
Serial.println(epoch); //print Unix time
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
return epoch + 28800;
}
String sendData(String command, const int timeout, boolean debug) {
String response="";
esp8266.print(command); // send the read character to the esp8266
long int time=millis();
while ((time+timeout) > millis()) {
while(esp8266.available()) {
// The esp has data so display its output to the serial window
char c=esp8266.read(); // read the next character.
response += c;
}
}
if (debug) {Serial.print(response);}
return response;
}
這裡我們新增了一個無傳回值的函數 sync_clock() 用來使 Arduino 與 NTP 時間同步, 然後於 setup() 中先初始同步一次後, 再用 Alarm.timeRepeat() 函式設定每 60 秒觸發 sync_clock() 一次. 而在 loop() 中則改用 Alarm.delay(1000) 函數來延遲一秒, 而非 delay(1000), 因為這樣才會每一秒去檢查事件狀態, 也就是 Alarm.timerRepeat() 所設定的 60 秒觸發是否已滿足.
下面測試 4 是將測試 3 無傳回值的 showDateTime() 改寫為傳回日期時間字串的 getDateTime(), 功能完全一樣 :
測試 4 :
#include <SoftwareSerial.h>
#define DEBUG true
#include <Time.h>
#include <TimeAlarms.h>
SoftwareSerial esp8266(7,8); //(RX,TX)
byte packetBuffer[128]; //buffer : send & recv data to/from NTP
void setup() {
Serial.begin(9600);
esp8266.begin(9600);
sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
sendData("AT+GMR\r\n",1000,DEBUG);
delay(3000); //wait for wifi connection to get local ip
sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
Serial.println("Sending request to NTP server ...");
sync_clock();
Alarm.timerRepeat(60, sync_clock);
}
void loop() {
Serial.println(getDateTime());
Alarm.delay(1000);
}
void sync_clock() {
setTime(getTime());
}
String getDateTime() { //傳回日期時間
String dt=(String)year() + "-";
byte M=month();
if (M < 10) {dt.concat('0');}
dt.concat(M);
dt.concat('-');
byte d=day();
if (d < 10) {dt.concat('0');}
dt.concat(d);
dt.concat(' ');
byte h=hour();
if (h < 10) {dt.concat('0');}
dt.concat(h);
dt.concat(':');
byte m=minute();
if (m < 10) {dt.concat('0');}
dt.concat(m);
dt.concat(':');
byte s=second();
if (s < 10) {dt.concat('0');}
dt.concat(s);
return dt; //傳回格式如 2016-07-16 16:09:23 的日期時間字串
}
long getTime() {
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n",5000,DEBUG);
memset(packetBuffer,0,128); //clear buffer
packetBuffer[0]=0xE3; //LI, Version, Mode
packetBuffer[1]=0x00; //Stratum, or type of clock
packetBuffer[2]=0x06; //Polling Interval
packetBuffer[3]=0xEC; //Peer Clock Precision
packetBuffer[12]=0x31; //reference ID (4 bytes)
packetBuffer[13]=0x4E;
packetBuffer[14]=0x31;
packetBuffer[15]=0x34;
//send request to NTP Server
sendData("AT+CIPSEND=48\r\n",1000,DEBUG); //send data length
for (byte i=0; i < 48; i++) {
esp8266.write(packetBuffer[i]);
delay(5);
}
//deal with NTP response
memset(packetBuffer,0,128); //clear buffer to store NTP response
Serial.println();
Serial.println("NTP server answered : ");
int i=0; //packet byte counter
while (esp8266.available() > 0) { //if receive NTP response : fall in loop
byte ch=esp8266.read(); //got NTP response, read one byte for each loop
if (i < 128) {packetBuffer[i]=ch;} //store received byte to packet buffer
//show receving bytes in hex
if (ch < 0x10) {Serial.print('0');} //prefix with '0' if byte value 0~9
Serial.print(ch, HEX);
Serial.print(' ');
if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
delay(5); //wait 5ms for next incoming byte
i++; //increment packet byte counter
if ((i < 104) && (esp8266.available() == 0)) { //wainting if lags
//Response packets not enough but no response : wait 1.5 seconds
byte wcount=0; //waiting counter
while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
Serial.print("!"); //show ! means waiting for response packet
delay(100);
wcount += 1; //increment waiting counter
if (wcount >= 15) {break;} //waiting timeout : quit loop
}
}
}
Serial.println();
Serial.println();
Serial.print(i+1);
Serial.println(" bytes received"); // will be more than 48
//Show time stamp (locates from byte 101~104 of the response packet)
Serial.print("NTP time stamp packets (byte 101~104)=");
Serial.print(packetBuffer[101],HEX);
Serial.print(" ");
Serial.print(packetBuffer[102],HEX);
Serial.print(" ");
Serial.print(packetBuffer[103],HEX);
Serial.print(" ");
Serial.print(packetBuffer[104],HEX);
Serial.println();
//handling time packets (4 bytes long) : combine them into words
unsigned long highWord=word(packetBuffer[101],packetBuffer[102]);
unsigned long lowWord=word(packetBuffer[103],packetBuffer[104]);
//shift high word 16 bits left & OR with low word to form a double word
//the result is a NTP time stamp (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("NTP time stamp (seconds since 1900-01-01)=");
Serial.println(secsSince1900);
//convert NTP time stamp to Unix time stamp :
//Unix time starts on Jan 1 1970=2208988800 seconds since 1900-01-01
//subtract seventy years to get Unix time stamp, plus 28800 (UTC+8)
unsigned long epoch=secsSince1900 - 2208988800UL;
Serial.print("Unix time stamp (seconds since 1970-01-01)=");
Serial.println(epoch); //print Unix time
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
return epoch + 28800;
}
String sendData(String command, const int timeout, boolean debug) {
String response="";
esp8266.print(command); // send the read character to the esp8266
long int time=millis();
while ((time+timeout) > millis()) {
while(esp8266.available()) {
// The esp has data so display its output to the serial window
char c=esp8266.read(); // read the next character.
response += c;
}
}
if (debug) {Serial.print(response);}
return response;
}
在測試 4 的 getDateTime() 函數中使用了 + 與字串的 concat() 兩種方式來串接日期字串. 此程式編譯後記憶體耗用結果 :
草稿碼使用了 10,838 bytes (35%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 866 bytes (42%) 的動態記憶體,剩餘 1,182 bytes 供局部變數。最大值為 2,048 bytes 。
最後一個測試我想把這個每分鐘與 NTP 同步的系統時鐘顯示在 1602 LCD 顯示器上, 參考之前的測試文章 :
# 以 Arduino + ESP8266 物聯網模組重作 NTP 實驗 (三)
在此測試中, LCD 上排顯示 IP, 下排顯示如 23:12:59 的時間, 因為 1602 顧名思義就是只能顯示 16*2 共 32 個字元, 而測試 4 中 getDateTime() 所傳回的是如 2016-07-16 16:57:02 格式的字串, 共 19 個字元, 無法於一排中全部顯示, 因此在下列測試 5 中, 我捨棄顯示 ip, 將傳回的日期時間字串以空格拆分後, 日期顯示在上排, 時間顯示在下排, 程式如下 :
測試 5 :
#include <SoftwareSerial.h>
#define DEBUG true
#include <Time.h>
#include <TimeAlarms.h>
#include <LiquidCrystal.h>
#define RS 2
#define E 3
#define D4 10
#define D5 11
#define D6 12
#define D7 13
SoftwareSerial esp8266(7,8); //(RX,TX)
byte packetBuffer[128]; //buffer : send & recv data to/from NTP
LiquidCrystal lcd(RS,E,D4,D5,D6,D7); //create LCD object
void setup() {
Serial.begin(9600);
esp8266.begin(9600);
sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
sendData("AT+GMR\r\n",1000,DEBUG);
delay(3000); //wait for wifi connection to get local ip
sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
Serial.println("Sending request to NTP server ...");
sync_clock();
Alarm.timerRepeat(60, sync_clock);
lcd.begin(16,2); //define 2*16 LCD
lcd.clear(); //clear screen
}
void loop() {
String dt=getDateTime();
Serial.println(dt);
String d=dt.substring(0,dt.indexOf(" ")); //以空格拆分日期與時間
String t=dt.substring(dt.indexOf(" ") + 1); //空格後為時間
lcd.setCursor(0,0); //move to (x,y)
lcd.print(d); //print date
lcd.setCursor(0,1); //move to (x,y)
lcd.print(t); //print time
Alarm.delay(1000);
}
void sync_clock() {
setTime(getTime());
}
String getDateTime() {
String dt=(String)year() + "-";
byte M=month();
if (M < 10) {dt.concat('0');}
dt.concat(M);
dt.concat('-');
byte d=day();
if (d < 10) {dt.concat('0');}
dt.concat(d);
dt.concat(' ');
byte h=hour();
if (h < 10) {dt.concat('0');}
dt.concat(h);
dt.concat(':');
byte m=minute();
if (m < 10) {dt.concat('0');}
dt.concat(m);
dt.concat(':');
byte s=second();
if (s < 10) {dt.concat('0');}
dt.concat(s);
return dt;
}
long getTime() {
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n",5000,DEBUG);
memset(packetBuffer,0,128); //clear buffer
packetBuffer[0]=0xE3; //LI, Version, Mode
packetBuffer[1]=0x00; //Stratum, or type of clock
packetBuffer[2]=0x06; //Polling Interval
packetBuffer[3]=0xEC; //Peer Clock Precision
packetBuffer[12]=0x31; //reference ID (4 bytes)
packetBuffer[13]=0x4E;
packetBuffer[14]=0x31;
packetBuffer[15]=0x34;
//send request to NTP Server
sendData("AT+CIPSEND=48\r\n",1000,DEBUG); //send data length
for (byte i=0; i < 48; i++) {
esp8266.write(packetBuffer[i]);
delay(5);
}
//deal with NTP response
memset(packetBuffer,0,128); //clear buffer to store NTP response
Serial.println();
Serial.println("NTP server answered : ");
int i=0; //packet byte counter
while (esp8266.available() > 0) { //if receive NTP response : fall in loop
byte ch=esp8266.read(); //got NTP response, read one byte for each loop
if (i < 128) {packetBuffer[i]=ch;} //store received byte to packet buffer
//show receving bytes in hex
if (ch < 0x10) {Serial.print('0');} //prefix with '0' if byte value 0~9
Serial.print(ch, HEX);
Serial.print(' ');
if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
delay(5); //wait 5ms for next incoming byte
i++; //increment packet byte counter
if ((i < 104) && (esp8266.available() == 0)) { //wainting if lags
//Response packets not enough but no response : wait 1.5 seconds
byte wcount=0; //waiting counter
while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
Serial.print("!"); //show ! means waiting for response packet
delay(100);
wcount += 1; //increment waiting counter
if (wcount >= 15) {break;} //waiting timeout : quit loop
}
}
}
Serial.println();
Serial.println();
Serial.print(i+1);
Serial.println(" bytes received"); // will be more than 48
//Show time stamp (locates from byte 101~104 of the response packet)
Serial.print("NTP time stamp packets (byte 101~104)=");
Serial.print(packetBuffer[101],HEX);
Serial.print(" ");
Serial.print(packetBuffer[102],HEX);
Serial.print(" ");
Serial.print(packetBuffer[103],HEX);
Serial.print(" ");
Serial.print(packetBuffer[104],HEX);
Serial.println();
//handling time packets (4 bytes long) : combine them into words
unsigned long highWord=word(packetBuffer[101],packetBuffer[102]);
unsigned long lowWord=word(packetBuffer[103],packetBuffer[104]);
//shift high word 16 bits left & OR with low word to form a double word
//the result is a NTP time stamp (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("NTP time stamp (seconds since 1900-01-01)=");
Serial.println(secsSince1900);
//convert NTP time stamp to Unix time stamp :
//Unix time starts on Jan 1 1970=2208988800 seconds since 1900-01-01
//subtract seventy years to get Unix time stamp, plus 28800 (UTC+8)
unsigned long epoch=secsSince1900 - 2208988800UL;
Serial.print("Unix time stamp (seconds since 1970-01-01)=");
Serial.println(epoch); //print Unix time
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
return epoch + 28800;
}
String sendData(String command, const int timeout, boolean debug) {
String response="";
esp8266.print(command); // send the read character to the esp8266
long int time=millis();
while ((time+timeout) > millis()) {
while(esp8266.available()) {
// The esp has data so display its output to the serial window
char c=esp8266.read(); // read the next character.
response += c;
}
}
if (debug) {Serial.print(response);}
return response;
}
程式上傳後運作正常, 秒數會每一秒跳一下, 只有在每分鐘要同步時會暫停跳動約 9~10 秒 :
請注意當時間跑到 16:44:03 時會停止約 9 秒, 然後直接跳到 16:44:12, 這 9 秒就是 NTP 同步所花的時間.
2 則留言 :
您好
我目前使用的RTC是DS3232
給予時間的來源為
setTime(14, 56, 59, 13, 2, 2017);
RTC.set(now());
setSyncProvider(RTC.get);
請問是否有方法能夠在燒錄時抓去當前電腦時間
我的目的是如果有一個以上的RTC,在燒錄後
只要不斷電就能夠使其達到時間同步的效果
您可以用 Time 函式庫在 setup() 中取得目前電腦時間填入 setTime() 中即可, 參考 :
http://playground.arduino.cc/Code/Time
張貼留言