2016年8月5日 星期五

★ 利用 NTP 伺服器來同步 Arduino 系統時鐘 (三)

今天寫完 "再探 NTP 協定" 後, 我想也該把之前的相關程式也改用新版作法, 首先是下面這篇 :

# 利用 NTP 伺服器來同步 Arduino 系統時鐘 (一)

下面是針對其中的測試 4 進行修改, 主要修改處是 sync_clock() 函數中 getUnixTime() 的傳回值需要加上 8 小時 (28800 秒) 才是台灣時間的秒數, 因為 getUnixTime() 傳回的是格林威治 (GMT) 自 1970/1/1 以來之秒數, 台灣是 GMT+8.

注意, 編譯下面程式前必須下載 TimeTimeAlarm 這兩個函式庫 :

https://github.com/PaulStoffregen/Time
https://github.com/PaulStoffregen/TimeAlarms

解壓縮後將 Time 與 TimeAlarm 兩個子目錄複製到 Arduino IDE 安裝目錄下的 libraries 下即可. 注意, libraries\Time\Time 這樣會抓不到函式庫, 正確為 libraries\Time. 其函式列表如下 :

 函式 說明
 hour() 傳回現在的時 (24 小時制)
 hourFormat12() 傳回現在的時 (12小時制)
 minute() 傳回現在的分
 second() 傳回現在的秒
 year() 傳回現在的年
 month() 傳回現在的月
 day() 傳回現在的日
 weekday() 傳回現在的星期 (星期日為 1)

其傳回值均為整數.

測試 4-1 :

#include <SoftwareSerial.h>
#define DEBUG true
#include <Time.h>
#include <TimeAlarms.h>

SoftwareSerial esp8266(7,8); //(RX,TX)
const String ssid="H30-L02-webbot";
const String pwd="1234567890";

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData(F("AT+RST\r\n"),2000,DEBUG); // reset ESP8266
  while (!connectWifi(ssid, pwd)) {
    Serial.println(F("Connecting WiFi ... failed"));
    delay(2000);
    }
  sendData(F("AT+GMR\r\n"),1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData(F("AT+CIFSR\r\n"),1000,DEBUG); //get ip address
  Serial.println(F("Sending request to NTP server ..."));
  sync_clock();
  Alarm.timerRepeat(60, sync_clock);
  }

void loop() {
  Serial.println(getDateTime());
  Alarm.delay(1000);
  }

void sync_clock() {
  setTime(getUnixTime() + 28800L);
  }

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 的日期時間字串
  }

unsigned long getUnixTime() {
  //NTP Server candidates : 91.226.136.136   82.209.243.241   192.5.41.40
  sendData(F("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n"),5000,DEBUG);
  byte packetBuffer[48]; //packet buffer : send/recv data to/from NTP
  memset(packetBuffer,0,48);  //clear packet buffer
  //configure packet buffer for requesting NTP server
  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 48-bytes request to NTP Server
  sendData(F("AT+CIPSEND=48\r\n"),1000,DEBUG); //send data length
  for (byte i=0; i < 48; i++) { //sending byte by byte
    esp8266.write(packetBuffer[i]);
    delay(1); //wait for writing
    }
  //processing NTP response
  memset(packetBuffer,0,48); //clear buffer to store NTP response
  Serial.println();
  if (esp8266.available() > 0) { //if receive NTP response
    if (esp8266.find("+IPD,48:")) { //search for IPD marker
      Serial.println(F("'+IPD,48:' found, NTP server answered :"));
      Serial.println();
      for (byte i=0; i<48; i++) { //read following 48 bytes from serial buffer  
        byte ch=esp8266.read(); //read one byte each time
        packetBuffer[i]=ch; //store byte packet buffer
        //show receving bytes in hex
        if (ch < 0x10) {Serial.print(F("0"));} //prefix with "0" if byte value 0~9
        Serial.print(ch, HEX);
        Serial.print(F(" ")); //space between each byte
        if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
        delay(1); //wait for next incoming byte
        if ((i < 48) && (esp8266.available() == 0)) { //wait if receiving lags
          //time packets not complete but no response : wait 1.5 seconds
          byte wcount=0; //waiting counter
          while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
            Serial.print(F("!")); //show ! means waiting for response packet
            delay(100);
            wcount += 1; //increment waiting counter
            if (wcount >= 15) {break;} //waiting timeout : quit loop
            } //end of while
          } //end of if
        } //end of for
      } //end of if
    } //end of if
  sendData(F("AT+CIPCLOSE\r\n"),1000,DEBUG); //close session
  Serial.println();
  Serial.println();
  //Show time stamp (locates at index 32~35 of the response packets)
  Serial.print(F("NTP time stamp packets (byte 32~35)="));
  Serial.print(packetBuffer[32],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[33],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[34],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[35],HEX);
  Serial.println();
  //combine 4 bytes time packets into words
  unsigned long highWord=word(packetBuffer[32],packetBuffer[33]);
  unsigned long lowWord=word(packetBuffer[34],packetBuffer[35]);
  //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(F("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
  unsigned long unix_time=secsSince1900 - 2208988800UL;
  Serial.print(F("Unix time stamp (seconds since 1970-01-01)="));
  Serial.println(unix_time); //print Unix time
  return unix_time; //return seconds since 1970-01-01
  }

boolean connectWifi(String ssid, String pwd) {
  String res=sendData("AT+CWJAP=\"" + ssid + F("\",\"") + pwd + F("\"\r\n"),8000,DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("OK") != -1) {return true;}
  else {return false;}
  }

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;
  }

此程式編譯後全域變數僅耗掉約 1/4 :

草稿碼使用了 12,752 bytes (41%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 500 bytes (24%) 的動態記憶體,剩餘 1,548 bytes 供局部變數。最大值為 2,048 bytes 。

在 "利用 NTP 伺服器來同步 Arduino 系統時鐘 (一)" 的測試 4 則耗去 41%, 當然這也要歸功於使用了 F() 來節省 SRAM 的耗用情形, 代價是程式記憶體消耗量從 35% 提升到 41%.

序列埠監控視窗擷取訊息如下 :

AT+GMR

0018000902

OK
AT+CIFSR

192.168.43.151

OK
Sending request to NTP server ...
AT+CIPSTART="UDP","91.226.136.136",123


OK
AT+CIPSEND=48

>
'+IPD,48:' found, NTP server answered :

24 02 06 ED 00 00 00 21 00 00
07 F2 6B DC 0B 87 DB 4E C0 61
93 8E 58 2E 00 00 00 00 00 00
00 00 DB 4E C5 E7 29 F8 29 79
DB 4E C5 E7 2A 7E 27 66

NTP time stamp packets (byte 32~35)=DB 4E C5 E7
NTP time stamp (seconds since 1900-01-01)=3679372775
Unix time stamp (seconds since 1970-01-01)=1470383975
2016-08-05 15:59:35
2016-08-05 15:59:36
2016-08-05 15:59:37
2016-08-05 15:59:38
2016-08-05 15:59:39
2016-08-05 15:59:40
2016-08-05 15:59:41
2016-08-05 15:59:42
2016-08-05 15:59:43
2016-08-05 15:59:44
2016-08-05 15:59:45
2016-08-05 15:59:46
2016-08-05 15:59:47
2016-08-05 15:59:48
2016-08-05 15:59:49
2016-08-05 15:59:50
2016-08-05 15:59:51
2016-08-05 15:59:52
2016-08-05 15:59:53
2016-08-05 15:59:54
2016-08-05 15:59:55
2016-08-05 15:59:56
2016-08-05 15:59:57
2016-08-05 15:59:58
2016-08-05 15:59:59
2016-08-05 16:00:00
2016-08-05 16:00:01
2016-08-05 16:00:02
2016-08-05 16:00:03
2016-08-05 16:00:04
2016-08-05 16:00:05
2016-08-05 16:00:06
2016-08-05 16:00:07
2016-08-05 16:00:08
2016-08-05 16:00:09
2016-08-05 16:00:10
2016-08-05 16:00:11
2016-08-05 16:00:12
2016-08-05 16:00:13
2016-08-05 16:00:14
2016-08-05 16:00:15
2016-08-05 16:00:16
2016-08-05 16:00:17
2016-08-05 16:00:18
2016-08-05 16:00:19
2016-08-05 16:00:20
2016-08-05 16:00:21
2016-08-05 16:00:22
2016-08-05 16:00:23
2016-08-05 16:00:24
2016-08-05 16:00:25
2016-08-05 16:00:26
2016-08-05 16:00:27
2016-08-05 16:00:28
2016-08-05 16:00:29
2016-08-05 16:00:30
2016-08-05 16:00:31
2016-08-05 16:00:32
2016-08-05 16:00:33
2016-08-05 16:00:34

OK
AT+CIPSTART="UDP","91.226.136.136",123

ALREAY CONNECT
AT+CIPSEND=48

>
'+IPD,48:' found, NTP server answered :

24 02 06 ED 00 00 00 21 00 00
08 34 6B DC 0B 87 DB 4E C0 61
93 8E 58 2E 00 00 00 00 00 00
00 00 DB 4E C6 2A 10 E3 30 0B
DB 4E C6 2A 10 ED 53 26

NTP time stamp packets (byte 32~35)=DB 4E C6 2A
NTP time stamp (seconds since 1900-01-01)=3679372842
Unix time stamp (seconds since 1970-01-01)=1470384042
2016-08-05 16:00:42
2016-08-05 16:00:43
2016-08-05 16:00:44
2016-08-05 16:00:45
2016-08-05 16:00:46
2016-08-05 16:00:47
2016-08-05 16:00:48
2016-08-05 16:00:49
.......

接著來幫下面這篇文章的程式改版 :

# 利用 NTP 伺服器來同步 Arduino 系統時鐘 (二)

首先是將原來的測試 6 改為 6-1 如下, 這篇是用 1602 LCD 液晶顯示器顯示日期時間與星期 :

測試 6-1 : 

#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)
const String ssid="H30-L02-webbot";
const String pwd="1234567890";
LiquidCrystal lcd(RS,E,D4,D5,D6,D7);  //create LCD object

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData(F("AT+RST\r\n"),2000,DEBUG); // reset ESP8266
  while (!connectWifi(ssid, pwd)) {
    Serial.println(F("Connecting WiFi ... failed"));
    delay(2000);
    }
  sendData(F("AT+GMR\r\n"),1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData(F("AT+CIFSR\r\n"),1000,DEBUG); //get ip address
  Serial.println(F("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 d=getDate();
  String t=getTime();
  String w=getWeek();
  Serial.println(d + " " + t + " " + w);
  lcd.setCursor(0,0); //move to (x,y)
  lcd.print(d); //print date
  lcd.setCursor(11,0); //move to (x,y)
  lcd.print(w); //print date
  lcd.setCursor(0,1); //move to (x,y)
  lcd.print(t); //print date
  Alarm.delay(1000);
  }

void sync_clock() {
  setTime(getUnixTime() + 28800L);
  }

String getDate() {
  String d=(String)year() + "-";
  byte M=month();
  if (M < 10) {d.concat('0');}
  d.concat(M);
  d.concat('-');
  byte D=day();
  if (D < 10) {d.concat('0');}
  d.concat(D);
  return d;
  }

String getTime() {
  String t="";
  byte h=hour();
  if (h < 10) {t.concat('0');}
  t.concat(h);
  t.concat(':');
  byte m=minute();
  if (m < 10) {t.concat('0');}
  t.concat(m);
  t.concat(':');
  byte s=second();
  if (s < 10) {t.concat('0');}
  t.concat(s);
  return t;
  }

String getWeek() {
  String w[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
  return w[weekday()-1];
  }

unsigned long getUnixTime() {
  //NTP Server candidates : 91.226.136.136   82.209.243.241   192.5.41.40
  sendData(F("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n"),5000,DEBUG);
  byte packetBuffer[48]; //packet buffer : send/recv data to/from NTP
  memset(packetBuffer,0,48);  //clear packet buffer
  //configure packet buffer for requesting NTP server
  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 48-bytes request to NTP Server
  sendData(F("AT+CIPSEND=48\r\n"),1000,DEBUG); //send data length
  for (byte i=0; i < 48; i++) { //sending byte by byte
    esp8266.write(packetBuffer[i]);
    delay(1); //wait for writing
    }
  //processing NTP response
  memset(packetBuffer,0,48); //clear buffer to store NTP response
  Serial.println();
  if (esp8266.available() > 0) { //if receive NTP response
    if (esp8266.find("+IPD,48:")) { //search for IPD marker
      Serial.println(F("'+IPD,48:' found, NTP server answered :"));
      Serial.println();
      for (byte i=0; i<48; i++) { //read following 48 bytes from serial buffer  
        byte ch=esp8266.read(); //read one byte each time
        packetBuffer[i]=ch; //store byte packet buffer
        //show receving bytes in hex
        if (ch < 0x10) {Serial.print(F("0"));} //prefix with "0" if byte value 0~9
        Serial.print(ch, HEX);
        Serial.print(F(" ")); //space between each byte
        if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
        delay(1); //wait for next incoming byte
        if ((i < 48) && (esp8266.available() == 0)) { //wait if receiving lags
          //time packets not complete but no response : wait 1.5 seconds
          byte wcount=0; //waiting counter
          while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
            Serial.print(F("!")); //show ! means waiting for response packet
            delay(100);
            wcount += 1; //increment waiting counter
            if (wcount >= 15) {break;} //waiting timeout : quit loop
            } //end of while
          } //end of if
        } //end of for
      } //end of if
    } //end of if
  sendData(F("AT+CIPCLOSE\r\n"),1000,DEBUG); //close session
  Serial.println();
  Serial.println();
  //Show time stamp (locates at index 32~35 of the response packets)
  Serial.print(F("NTP time stamp packets (byte 32~35)="));
  Serial.print(packetBuffer[32],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[33],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[34],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[35],HEX);
  Serial.println();
  //combine 4 bytes time packets into words
  unsigned long highWord=word(packetBuffer[32],packetBuffer[33]);
  unsigned long lowWord=word(packetBuffer[34],packetBuffer[35]);
  //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(F("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
  unsigned long unix_time=secsSince1900 - 2208988800UL;
  Serial.print(F("Unix time stamp (seconds since 1970-01-01)="));
  Serial.println(unix_time); //print Unix time
  return unix_time; //return seconds since 1970-01-01
  }

boolean connectWifi(String ssid, String pwd) {
  String res=sendData("AT+CWJAP=\"" + ssid + F("\",\"") + pwd + F("\"\r\n"),8000,DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("OK") != -1) {return true;}
  else {return false;}
  }

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;
  }

此程式編譯後記憶體耗用情形 :

草稿碼使用了 14,032 bytes (45%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 562 bytes (27%) 的動態記憶體,剩餘 1,486 bytes 供局部變數。最大值為 2,048 bytes 。

最後是將原測試 7 加上溫溼度感測器的程式, 這必須先到 Github 下載 Adafruit 的 DHT11/22 函式庫到 Arduino 安裝目錄下的 library 資料夾下 (Arduino IDE 必須重開才抓得到) :

adafruit/DHT-sensor-library

測試 7-1 :

#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

#include "DHT.h"
#define DHTPIN 6   
#define DHTTYPE DHT11

SoftwareSerial esp8266(7,8); //(RX,TX)
const String ssid="H30-L02-webbot";
const String pwd="1234567890";
LiquidCrystal lcd(RS,E,D4,D5,D6,D7);  //create LCD object
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData(F("AT+RST\r\n"),2000,DEBUG); // reset ESP8266
  while (!connectWifi(ssid, pwd)) {
    Serial.println(F("Connecting WiFi ... failed"));
    delay(2000);
    }
  sendData(F("AT+GMR\r\n"),1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData(F("AT+CIFSR\r\n"),1000,DEBUG); //get ip address
  Serial.println(F("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 d=getDate();
  String t=getTime();
  String w=getWeek();
  Serial.println(d + " " + t + " " + w);
  lcd.setCursor(0,0); //move to (x,y)
  lcd.print(d); //print date
  lcd.setCursor(11,0); //move to (x,y)
  lcd.print(w); //print date
  lcd.setCursor(0,1); //move to (x,y)
  lcd.print(t); //print date
  float h=dht.readHumidity();        //get humidity from DHT11
  float c=dht.readTemperature();     //get temperature (C) from DHT11 
  if (isnan(h) || isnan(c)) {h=0.0;c=0.0;}
  lcd.setCursor(9,1); //move to (x,y)
  lcd.print(int(c)); //print celcius  
  lcd.setCursor(11,1); //move to (x,y)
  lcd.print((char)223); //print degree symbol
  lcd.setCursor(12,1); //move to (x,y)
  lcd.print(' '); //print space   
  lcd.setCursor(13,1); //move to (x,y)
  lcd.print(int(h)); //print humidity 
  lcd.setCursor(15,1); //move to (x,y)
  lcd.print('%'); //print %        
  Alarm.delay(1000);
  }

void sync_clock() {
  setTime(getUnixTime() + 28800L);
  }

String getDate() {
  String d=(String)year() + "-";
  byte M=month();
  if (M < 10) {d.concat('0');}
  d.concat(M);
  d.concat('-');
  byte D=day();
  if (D < 10) {d.concat('0');}
  d.concat(D);
  return d;
  }

String getTime() {
  String t="";
  byte h=hour();
  if (h < 10) {t.concat('0');}
  t.concat(h);
  t.concat(':');
  byte m=minute();
  if (m < 10) {t.concat('0');}
  t.concat(m);
  t.concat(':');
  byte s=second();
  if (s < 10) {t.concat('0');}
  t.concat(s);
  return t;
  }

String getWeek() {
  String w[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
  return w[weekday()-1];
  }

unsigned long getUnixTime() {
  //NTP Server candidates : 91.226.136.136   82.209.243.241   192.5.41.40
  sendData(F("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n"),5000,DEBUG);
  byte packetBuffer[48]; //packet buffer : send/recv data to/from NTP
  memset(packetBuffer,0,48);  //clear packet buffer
  //configure packet buffer for requesting NTP server
  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 48-bytes request to NTP Server
  sendData(F("AT+CIPSEND=48\r\n"),1000,DEBUG); //send data length
  for (byte i=0; i < 48; i++) { //sending byte by byte
    esp8266.write(packetBuffer[i]);
    delay(1); //wait for writing
    }
  //processing NTP response
  memset(packetBuffer,0,48); //clear buffer to store NTP response
  Serial.println();
  if (esp8266.available() > 0) { //if receive NTP response
    if (esp8266.find("+IPD,48:")) { //search for IPD marker
      Serial.println(F("'+IPD,48:' found, NTP server answered :"));
      Serial.println();
      for (byte i=0; i<48; i++) { //read following 48 bytes from serial buffer  
        byte ch=esp8266.read(); //read one byte each time
        packetBuffer[i]=ch; //store byte packet buffer
        //show receving bytes in hex
        if (ch < 0x10) {Serial.print(F("0"));} //prefix with "0" if byte value 0~9
        Serial.print(ch, HEX);
        Serial.print(F(" ")); //space between each byte
        if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
        delay(1); //wait for next incoming byte
        if ((i < 48) && (esp8266.available() == 0)) { //wait if receiving lags
          //time packets not complete but no response : wait 1.5 seconds
          byte wcount=0; //waiting counter
          while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
            Serial.print(F("!")); //show ! means waiting for response packet
            delay(100);
            wcount += 1; //increment waiting counter
            if (wcount >= 15) {break;} //waiting timeout : quit loop
            } //end of while
          } //end of if
        } //end of for
      } //end of if
    } //end of if
  sendData(F("AT+CIPCLOSE\r\n"),1000,DEBUG); //close session
  Serial.println();
  Serial.println();
  //Show time stamp (locates at index 32~35 of the response packets)
  Serial.print(F("NTP time stamp packets (byte 32~35)="));
  Serial.print(packetBuffer[32],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[33],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[34],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[35],HEX);
  Serial.println();
  //combine 4 bytes time packets into words
  unsigned long highWord=word(packetBuffer[32],packetBuffer[33]);
  unsigned long lowWord=word(packetBuffer[34],packetBuffer[35]);
  //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(F("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
  unsigned long unix_time=secsSince1900 - 2208988800UL;
  Serial.print(F("Unix time stamp (seconds since 1970-01-01)="));
  Serial.println(unix_time); //print Unix time
  return unix_time; //return seconds since 1970-01-01
  }

boolean connectWifi(String ssid, String pwd) {
  String res=sendData("AT+CWJAP=\"" + ssid + F("\",\"") + pwd + F("\"\r\n"),8000,DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("OK") != -1) {return true;}
  else {return false;}
  }

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;
  }

此程式編譯後記憶體耗用情形 :

草稿碼使用了 16,422 bytes (53%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 580 bytes (28%) 的動態記憶體,剩餘 1,468 bytes 供局部變數。最大值為 2,048 bytes 。

如果使用我的 Arduino+ESP8266 IOT 模組的話, 可以隨時用手機更改要連線的 ssid 與 pwd, 不需要修改程式中的這兩個變數再上傳, 比較方便, 程式架構參考 :

# 製作 Arduino Nano + ESP8266 物聯網模組 (四) : 完結篇

測試 7-2 :

#include <SoftwareSerial.h>
#define DEBUG true
//-----application include & def listed HERE-----
#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
#include "DHT.h"
#define DHTPIN 6
#define DHTTYPE DHT11

//system use please do not edit
SoftwareSerial esp8266(7,8); //(RX,TX)
const int SW_PIN=4; //Pin to switch configuration or working mode
const int MAX_PAGE_NAME_LEN=48;  //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name/ssid/pwd
int mode; //store current mode(LOW=wifi setup, HIGH=working)

//-----application global variables listed HERE-----
LiquidCrystal lcd(RS,E,D4,D5,D6,D7);  //create LCD object
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor

void setup() {
  //system use please do not edit
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData(F("AT+RST\r\n"),2000,DEBUG); // reset ESP8266
  pinMode(SW_PIN, INPUT);
  mode=digitalRead(SW_PIN);
  sendData(F("AT+GMR\r\n"),1000,DEBUG);
  if (mode==LOW) { //setup mode : for wifi configuration
    sendData(F("AT+CWMODE=3\r\n"),1000,DEBUG); //configure as access point
    sendData(F("AT+CIPMUX=1\r\n"),1000,DEBUG); //enable multiple connections
    sendData(F("AT+CIPSERVER=1,80\r\n"),2000,DEBUG); //turn on server 80 port  
    }
  else {  //working mode : for running application
    sendData(F("AT+CWMODE=1\r\n"),1000,DEBUG); //configure as a station
    delay(3000); //wait for wifi connection to get local ip
    }
  sendData(F("AT+CIFSR\r\n"),1000,DEBUG); //get ip address
  //-----application setup codes listed HERE-----
  Serial.println(F("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() {
  if (mode==LOW) {setupWifi();} //system use please do not edit
  else { //-----application loop codes listed HERE-----
    String d=getDate();
    String t=getTime();
    String w=getWeek();
    Serial.print(d + F(" ") + t + F(" ") + w + F(" "));
    lcd.setCursor(0,0); //move to (x,y)
    lcd.print(d); //print date
    lcd.setCursor(11,0); //move to (x,y)
    lcd.print(w); //print date
    lcd.setCursor(0,1); //move to (x,y)
    lcd.print(t); //print date
    float h=dht.readHumidity();        //get humidity from DHT11
    float c=dht.readTemperature();     //get temperature (C) from DHT11
    if (isnan(h) || isnan(c)) {h=0.0;c=0.0;}
    Serial.print(int(c));
    Serial.print(F(" degree "));
    Serial.print(int(h));
    Serial.println(F("%"));
    lcd.setCursor(9,1); //move to (x,y)
    lcd.print(int(c)); //print celcius
    lcd.setCursor(11,1); //move to (x,y)
    lcd.print((char)223); //print degree symbol
    lcd.setCursor(12,1); //move to (x,y)
    lcd.print(F(" ")); //print space
    lcd.setCursor(13,1); //move to (x,y)
    lcd.print(int(h)); //print humidity
    lcd.setCursor(15,1); //move to (x,y)
    lcd.print(F("%")); //print %      
    Alarm.delay(1000);
    }
  }

void sync_clock() {
  setTime(getUnixTime() + 28800L);
  }

String getDate() {
  String d=(String)year() + "-";
  byte M=month();
  if (M < 10) {d.concat('0');}
  d.concat(M);
  d.concat('-');
  byte D=day();
  if (D < 10) {d.concat('0');}
  d.concat(D);
  return d;
  }

String getTime() {
  String t="";
  byte h=hour();
  if (h < 10) {t.concat('0');}
  t.concat(h);
  t.concat(':');
  byte m=minute();
  if (m < 10) {t.concat('0');}
  t.concat(m);
  t.concat(':');
  byte s=second();
  if (s < 10) {t.concat('0');}
  t.concat(s);
  return t;
  }

String getWeek() {
  String w[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
  return w[weekday()-1];
  }

unsigned long getUnixTime() {
  //NTP Server candidates : 91.226.136.136   82.209.243.241   192.5.41.40
  sendData(F("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n"),5000,DEBUG);
  byte packetBuffer[48]; //packet buffer : send/recv data to/from NTP
  memset(packetBuffer,0,48);  //clear packet buffer
  //configure packet buffer for requesting NTP server
  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 48-bytes request to NTP Server
  sendData(F("AT+CIPSEND=48\r\n"),1000,DEBUG); //send data length
  for (byte i=0; i < 48; i++) { //sending byte by byte
    esp8266.write(packetBuffer[i]);
    delay(1); //wait for writing
    }
  //processing NTP response
  memset(packetBuffer,0,48); //clear buffer to store NTP response
  Serial.println();
  if (esp8266.available() > 0) { //if receive NTP response
    if (esp8266.find("+IPD,48:")) { //search for IPD marker
      Serial.println(F("'+IPD,48:' found, NTP server answered :"));
      Serial.println();
      for (byte i=0; i<48; i++) { //read following 48 bytes from serial buffer
        byte ch=esp8266.read(); //read one byte each time
        packetBuffer[i]=ch; //store byte packet buffer
        //show receving bytes in hex
        if (ch < 0x10) {Serial.print(F("0"));} //prefix with "0" if byte value 0~9
        Serial.print(ch, HEX);
        Serial.print(F(" ")); //space between each byte
        if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
        delay(1); //wait for next incoming byte
        if ((i < 48) && (esp8266.available() == 0)) { //wait if receiving lags
          //time packets not complete but no response : wait 1.5 seconds
          byte wcount=0; //waiting counter
          while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
            Serial.print(F("!")); //show ! means waiting for response packet
            delay(100);
            wcount += 1; //increment waiting counter
            if (wcount >= 15) {break;} //waiting timeout : quit loop
            } //end of while
          } //end of if
        } //end of for
      } //end of if
    } //end of if
  sendData(F("AT+CIPCLOSE\r\n"),1000,DEBUG); //close session
  Serial.println();
  Serial.println();
  //Show time stamp (locates at index 32~35 of the response packets)
  Serial.print(F("NTP time stamp packets (byte 32~35)="));
  Serial.print(packetBuffer[32],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[33],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[34],HEX);
  Serial.print(F(" "));
  Serial.print(packetBuffer[35],HEX);
  Serial.println();
  //combine 4 bytes time packets into words
  unsigned long highWord=word(packetBuffer[32],packetBuffer[33]);
  unsigned long lowWord=word(packetBuffer[34],packetBuffer[35]);
  //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(F("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
  unsigned long unix_time=secsSince1900 - 2208988800UL;
  Serial.print(F("Unix time stamp (seconds since 1970-01-01)="));
  Serial.println(unix_time); //print Unix time
  return unix_time; //return seconds since 1970-01-01
  }

void setupWifi() { //system use please do not edit
  if (esp8266.available()) { // check if the esp is sending a message
    if (esp8266.find("+IPD,")) {
      delay(1000);
      //esp8266 link response : +IPD,0,498:GET / HTTP/1.1
      //retrieve connection ID from response (0~4, after "+IPD,")
      int connectionId=esp8266.read()-48;  //from ASCII to number
      //subtract 48 because read() returns ASCII decimal value
      //and in ASCII, "0" (the first decimal number) starts at 48
      if (esp8266.find("GET /")) { //retrieve page name (router)
        memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
        if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
          if (strcmp(buffer, "update") == 0) { //update wifi
            //"?ssid=aaa&pwd=bbb HTTP/1.1"      
            esp8266.find("?ssid="); //skip ssid token
            memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
            esp8266.readBytesUntil('&', buffer, sizeof(buffer)); //retrieve ssid
            String ssid=buffer;
            esp8266.find("pwd="); //skip pwd token
            memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
            esp8266.readBytesUntil(' ', buffer, sizeof(buffer)); //retrieve pwd
            String pwd=buffer;
            //configure as a station
            String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",6000,DEBUG);
            //show setup result
            String webpage=F("<html>Wifi setup ");
            if (res.indexOf("OK") != -1) {webpage += F("OK!</html>");}
            else {webpage += F("Failed!</html>");}
            String cipSend=F("AT+CIPSEND=");
            cipSend += connectionId;
            cipSend += F(",");
            cipSend += webpage.length();
            cipSend += F("\r\n");
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);      
            String closeCommand=F("AT+CIPCLOSE=");
            closeCommand += connectionId; // append connection id
            closeCommand += F("\r\n");
            sendData(closeCommand,3000,DEBUG);          
            }
          else { //show setup page
            String webpage=F("<html><form method=get action='/update/'>SSID ");
            webpage += F("<input name=ssid type=text><br>");
            String cipSend=F("AT+CIPSEND=");
            cipSend += connectionId;
            cipSend += F(",");
            cipSend += webpage.length();
            cipSend += F("\r\n");
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);
            webpage=F("PWD <input name=pwd type=text> ");
            cipSend=F("AT+CIPSEND=");
            cipSend += connectionId;
            cipSend += F(",");
            cipSend += webpage.length();
            cipSend += F("\r\n");
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);
            webpage=F("<input type=submit value=Connect></form></html>");
            cipSend = F("AT+CIPSEND=");
            cipSend += connectionId;
            cipSend += F(",");
            cipSend += webpage.length();
            cipSend += F("\r\n");
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);
            String closeCommand =F("AT+CIPCLOSE=");
            closeCommand += connectionId; // append connection id
            closeCommand += F("\r\n");
            sendData(closeCommand,3000,DEBUG);
            }
          }
        }
      }
    }
  }

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;
  }

編譯後記憶體耗用情形 :

草稿碼使用了 17,992 bytes (58%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 629 bytes (30%) 的動態記憶體,剩餘 1,419 bytes 供局部變數。最大值為 2,048 bytes 。

與上面比起來, 全域變數僅多 2%, 程式多 5% 而已.


6 則留言:

  1. 您好

    您所寫的文章對我而言有很大的幫助,在此非常感謝您專業的分享。
    參考了您此篇範例的4-1程式碼,但是一直無法抓到正確的時間,不知道應該怎麼解決,謝謝您。

    console不會出現
    '+IPD,48:' found, NTP server answered :

    24 02 06 ED 00 00 00 21 00 00
    07 F2 6B DC 0B 87 DB 4E C0 61
    93 8E 58 2E 00 00 00 00 00 00
    00 00 DB 4E C5 E7 29 F8 29 79
    DB 4E C5 E7 2A 7E 27 66
    這段

    且 顯示出
    NTP time stamp packets (byte 32~35)=0 0 0 0
    NTP time stamp (seconds since 1900-01-01)=0
    Unix time stamp (seconds since 1970-01-01)=2085978496
    2036-02-17 14:28:16
    2036-02-17 14:28:17
    2036-02-17 14:28:18
    2036-02-17 14:28:19
    2036-02-17 14:28:20
    這樣的錯誤資訊

    想請問您給予修正的建議 謝謝您

    回覆刪除
  2. 您好

    您是說 ssid和password嗎
    是有改的

    謝謝您

    回覆刪除
  3. Lulu 您好,

    收到 +IPD 表示 NTP 有回應, 沒收到就是 "NO RESPONSE", 改用別的 NTP SERVER 看看, 例如 NIST (time.nist.gov) 的, 參考 :

    https://tf.nist.gov/tf-cgi/servers.cgi

    回覆刪除
  4. 不好意思 我這邊照用你的程式
    卻無法連上網 帳密的部分一直被改字元
    請問版主有遇過這種狀況嗎 麻煩解惑了 感謝

    序列複訊息如下:

    ready
    AT+CWJAP="A113","0922630290#


    ERQOR
    Connecting WiFi ... failed
    AT+CWJAP="A213",#0922630290"

    ERROR
    Connecting WiFi ... failed
    AT+CWJAP="A213",#0922630290"

    ERROR
    Connecting WiFi ... failed
    AT+CW⸮AP="A213",#0922630290#


    ERROR
    Connecting WiFi ... failed
    AT+CWJAP="A213",#0922630290"

    ERROR
    Connecting WiFi ... failed

    回覆刪除
  5. 可能是接線不良, 或者是 ESP8266 供電不足, 不要用 Arduino 的 3.3v 輸出, 試試看獨立的 3.3V 電源.

    回覆刪除