# 關於用 Arduino+ESP8266 進行網路對時的問題
# 使用 ITEAD 的 WeeESP8266 函式庫進行網路對時
雖然 WeeESP8266 函式庫完整好用, 但是載入整個函式庫也耗掉不少記憶體與可用的變數空間, 應用程式碼稍大些可能導致編譯警告或執行不正常. 這次我要使用從 AllAboutEE 修改而來的單一函式, 雖然不像 WeeESP8266 那麼方便, 必須接觸 AT 指令, 但程式編譯後的程式碼精簡較不占位置.
我參考下列這篇文章中 adminarduinocc 所貼的 Working codes 程式 :
我將其修改為 getTime() 函數並加入之前完結篇裡 Arduino + ESP8266 物聯網所使用的程式架構內, 在 loop() 中呼叫它以便取得 NTP 伺服器所回應的時間. 完整程式如下 :
#include <SoftwareSerial.h>
#define DEBUG true
//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=configuration, HIGH=working)
//-----application codes listed bellow-----
//Request for NTP time stamp (in the first 48 bytes)
byte packetBuffer[48]; //buffer : send & recv data to/from NTP
void setup() {
//system use please do not edit
Serial.begin(9600);
esp8266.begin(9600);
sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
pinMode(SW_PIN, INPUT);
mode=digitalRead(SW_PIN);
if (mode==LOW) { //wifi configuration mode :
sendData("AT+CWMODE=3\r\n",1000,DEBUG); //configure as access point
sendData("AT+CIPMUX=1\r\n",1000,DEBUG); //enable multiple connections
sendData("AT+CIPSERVER=1,80\r\n",1000,DEBUG); //turn on server 80 port
}
else {
sendData("AT+CWMODE?\r\n",1000,DEBUG);
sendData("AT+CIPMUX?\r\n",1000,DEBUG);
delay(3000);
}
sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
//-----application codes listed bellow-----
}
void loop() {
if (mode==LOW) {setupWifi();}
else { //-----application codes listed bellow-----
Serial.println("Getting time..");
int Epoch=GetTime();
Serial.print("Epoch is: ");
Serial.println(Epoch);
delay(5000);
}
}
unsigned long GetTime() {
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"192.5.41.40\",123\r\n",5000,DEBUG);
memset(packetBuffer,0,48); //clear buffer
//Initialize buffer to form NTP request packet
packetBuffer[0]=0b11100011; //LI, Version, Mode
packetBuffer[1]=0; //Stratum, or type of clock
packetBuffer[2]=6; //Polling Interval
packetBuffer[3]=0xEC; //Peer Clock Precision
packetBuffer[12]=49; //reference ID (4 bytes)
packetBuffer[13]=0x4E;
packetBuffer[14]=49;
packetBuffer[15]=52;
//send request to NTP Server
sendData("AT+CIPSEND=48\r\n",1000,DEBUG); //send data length
if (esp8266.find(">")) { //sending data to NTP if available
for (byte i=0; i < 48; i++) {
esp8266.write(packetBuffer[i]);
delay(5);
}
Serial.println("Send request ... OK");
}
else {
sendData("AT+CIPCLOSE\r\n",1000,DEBUG);
return 0;
}
delay(3000); //waiting for NTP Server response
int counta=0; //wainting counter
memset(packetBuffer,0,48); //clear buffer for receiving
if (Serial.find("+IPD,48:")) { //if NTP Server responds
Serial.println("Server answer : ");
int i=0; //response packet byte counter
while (esp8266.available() > 0) { //read response into packet buffer
byte ch=esp8266.read();
if (i < 48) {packetBuffer[i]=ch;} //read into buffer
i++; //increment packet counter
if ((i < 48) && (esp8266.available() == 0)) {
//Packet not complete but no response : wait 1.5s for response
while (esp8266.available() == 0) { //
counta += 1; //increment waiting counter
Serial.print("!"); //show ! means waiting for response packet
delay(100);
if (counta == 15) {exit;} //waiting timeout
}
}
}
}
//handling response packet : timestamp starts at byte 40 of response packet
//(4 bytes long), extract them into words :
unsigned long highWord=word(packetBuffer[40],packetBuffer[41]);
unsigned long lowWord=word(packetBuffer[42],packetBuffer[43]);
//combine the two words into a long integer time stamp
//this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900=" );
Serial.println(secsSince1900);
//convert NTP time into everyday time:
Serial.print("Unix time=");
//Unix time starts on Jan 1 1970=2208988800 seconds since 1900-01-01
const unsigned long seventyYears=2208988800UL;
//subtract seventy years to get Unix time stamp
unsigned long epoch=secsSince1900 - seventyYears;
Serial.println(epoch); //print Unix time
//Covert epoch to UTC/GMT (Greenwich Meridian) hour:minute:second
Serial.print("The UTC time is "); // UTC is the time at
Serial.print((epoch % 86400L) / 3600); //hour (86400 secs per day)
Serial.print(':');
if (((epoch % 3600) / 60) < 10) {Serial.print('0');}
Serial.print((epoch % 3600) / 60); //minute (3600 secs per minute)
Serial.print(':');
if ((epoch % 60) < 10 ) {Serial.print('0');}
Serial.println(epoch % 60); //second
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
}
void setupWifi() {
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="<html>Wifi setup ";
if (res.indexOf("OK") != -1) {webpage += "OK!</html>";}
else {webpage += "Failed!</html>";}
String cipSend="AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
String closeCommand = "AT+CIPCLOSE=";
closeCommand+=connectionId; // append connection id
closeCommand+="\r\n";
sendData(closeCommand,3000,DEBUG);
}
else { //show setup page
String webpage="<html><form method=get action='/update/'>SSID ";
webpage += "<input name=ssid type=text><br>";
String cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
webpage="PWD <input name=pwd type=text> ";
cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
webpage="<input type=submit value=Connect></form></html>";
cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
String closeCommand = "AT+CIPCLOSE=";
closeCommand+=connectionId; // append connection id
closeCommand+="\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;
}
#define _SS_MAX_RX_BUFF 128 // RX buffer size
#define DEBUG true
//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=configuration, HIGH=working)
//-----application codes listed bellow-----
//Request for NTP time stamp (in the first 48 bytes)
byte packetBuffer[48]; //buffer : send & recv data to/from NTP
void setup() {
//system use please do not edit
Serial.begin(9600);
esp8266.begin(9600);
sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
pinMode(SW_PIN, INPUT);
mode=digitalRead(SW_PIN);
if (mode==LOW) { //wifi configuration mode :
sendData("AT+CWMODE=3\r\n",1000,DEBUG); //configure as access point
sendData("AT+CIPMUX=1\r\n",1000,DEBUG); //enable multiple connections
sendData("AT+CIPSERVER=1,80\r\n",1000,DEBUG); //turn on server 80 port
}
else {
sendData("AT+CWMODE?\r\n",1000,DEBUG);
sendData("AT+CIPMUX?\r\n",1000,DEBUG);
delay(3000);
}
sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
//-----application codes listed bellow-----
}
void loop() {
if (mode==LOW) {setupWifi();}
else { //-----application codes listed bellow-----
Serial.println("Getting time..");
int Epoch=GetTime();
Serial.print("Epoch is: ");
Serial.println(Epoch);
delay(5000);
}
}
unsigned long GetTime() {
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"192.5.41.40\",123\r\n",5000,DEBUG);
memset(packetBuffer,0,48); //clear buffer
//Initialize buffer to form NTP request packet
packetBuffer[0]=0b11100011; //LI, Version, Mode
packetBuffer[1]=0; //Stratum, or type of clock
packetBuffer[2]=6; //Polling Interval
packetBuffer[3]=0xEC; //Peer Clock Precision
packetBuffer[12]=49; //reference ID (4 bytes)
packetBuffer[13]=0x4E;
packetBuffer[14]=49;
packetBuffer[15]=52;
//send request to NTP Server
sendData("AT+CIPSEND=48\r\n",1000,DEBUG); //send data length
if (esp8266.find(">")) { //sending data to NTP if available
for (byte i=0; i < 48; i++) {
esp8266.write(packetBuffer[i]);
delay(5);
}
Serial.println("Send request ... OK");
}
else {
sendData("AT+CIPCLOSE\r\n",1000,DEBUG);
return 0;
}
delay(3000); //waiting for NTP Server response
int counta=0; //wainting counter
memset(packetBuffer,0,48); //clear buffer for receiving
if (Serial.find("+IPD,48:")) { //if NTP Server responds
Serial.println("Server answer : ");
int i=0; //response packet byte counter
while (esp8266.available() > 0) { //read response into packet buffer
byte ch=esp8266.read();
if (i < 48) {packetBuffer[i]=ch;} //read into buffer
i++; //increment packet counter
if ((i < 48) && (esp8266.available() == 0)) {
//Packet not complete but no response : wait 1.5s for response
while (esp8266.available() == 0) { //
counta += 1; //increment waiting counter
Serial.print("!"); //show ! means waiting for response packet
delay(100);
if (counta == 15) {exit;} //waiting timeout
}
}
}
}
//handling response packet : timestamp starts at byte 40 of response packet
//(4 bytes long), extract them into words :
unsigned long highWord=word(packetBuffer[40],packetBuffer[41]);
unsigned long lowWord=word(packetBuffer[42],packetBuffer[43]);
//combine the two words into a long integer time stamp
//this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900=" );
Serial.println(secsSince1900);
//convert NTP time into everyday time:
Serial.print("Unix time=");
//Unix time starts on Jan 1 1970=2208988800 seconds since 1900-01-01
const unsigned long seventyYears=2208988800UL;
//subtract seventy years to get Unix time stamp
unsigned long epoch=secsSince1900 - seventyYears;
Serial.println(epoch); //print Unix time
//Covert epoch to UTC/GMT (Greenwich Meridian) hour:minute:second
Serial.print("The UTC time is "); // UTC is the time at
Serial.print((epoch % 86400L) / 3600); //hour (86400 secs per day)
Serial.print(':');
if (((epoch % 3600) / 60) < 10) {Serial.print('0');}
Serial.print((epoch % 3600) / 60); //minute (3600 secs per minute)
Serial.print(':');
if ((epoch % 60) < 10 ) {Serial.print('0');}
Serial.println(epoch % 60); //second
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
}
void setupWifi() {
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="<html>Wifi setup ";
if (res.indexOf("OK") != -1) {webpage += "OK!</html>";}
else {webpage += "Failed!</html>";}
String cipSend="AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
String closeCommand = "AT+CIPCLOSE=";
closeCommand+=connectionId; // append connection id
closeCommand+="\r\n";
sendData(closeCommand,3000,DEBUG);
}
else { //show setup page
String webpage="<html><form method=get action='/update/'>SSID ";
webpage += "<input name=ssid type=text><br>";
String cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
webpage="PWD <input name=pwd type=text> ";
cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
webpage="<input type=submit value=Connect></form></html>";
cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
String closeCommand = "AT+CIPCLOSE=";
closeCommand+=connectionId; // append connection id
closeCommand+="\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;
}
但是編譯上傳後卻沒有如預期取得 NTP 的回應, 序列埠監控視窗擷取的訊息如下 :
AT+RST
OK
bB�鑭b禔S��"��岓��S��
[Vendor:www.ai-thinker.com Version:0.9.2.4]
ready
AT+CWMODE?
+CWMODE:1
OK
AT+CIPMUX?
+CIPMUX:0
OK
AT+CIFSR
192.168.2.110
OK
Getting time..
AT+CIPSTART="UDP","82.209.243.241",123
OK
AT+CIPSEND=48
> AT+CIPCLOSE
Epoch is: 0
Getting time..
AT+CIPSTART="UDP","82.209.243.241",123
wrong syntax
ERROR
SEND OK
AT+CIPSEND=48
> AT+CIPCLOSE
Epoch is: 0
Getting time..
AT+CIPSTART="UDP","82.209.243.241",123
wrong syntax
ERROR
SEND OK
AT+CIPSEND=48
> AT+CIPCLOSE
Epoch is: 0
OK
bB�鑭b禔S��"��岓��S��
[Vendor:www.ai-thinker.com Version:0.9.2.4]
ready
AT+CWMODE?
+CWMODE:1
OK
AT+CIPMUX?
+CIPMUX:0
OK
AT+CIFSR
192.168.2.110
OK
Getting time..
AT+CIPSTART="UDP","82.209.243.241",123
OK
AT+CIPSEND=48
> AT+CIPCLOSE
Epoch is: 0
Getting time..
AT+CIPSTART="UDP","82.209.243.241",123
wrong syntax
ERROR
SEND OK
AT+CIPSEND=48
> AT+CIPCLOSE
Epoch is: 0
Getting time..
AT+CIPSTART="UDP","82.209.243.241",123
wrong syntax
ERROR
SEND OK
AT+CIPSEND=48
> AT+CIPCLOSE
Epoch is: 0
看起來似乎並非 NTP 伺服器有問題, 而是發出 Request 沒有成功, 導致收不到 NTP 的回應所致. Why? 到底哪裡出錯?
我看了下面這篇文章, 裡面提到 NTP 的回應會大於 84 bytes, 超過序列埠緩衝器的預設值 (64 bytes), 建議放寬為 128 bytes :
# Updated comment for UdpNTPClient
"NOTE: The serial buffer size must be larger than 36 + packet size
In this example we use an UDP packet of 48 bytes so the buffer must be
at least 36+48=84 bytes that exceeds the default buffer size (64).
You must modify the serial buffer size to 128
For HardwareSerial modify _SS_MAX_RX_BUFF in
Arduino\hardware\arduino\avr\cores\arduino\SoftwareSerial.h
For SoftwareSerial modify _SS_MAX_RX_BUFF in
Arduino\hardware\arduino\avr\libraries\SoftwareSerial\SoftwareSerial.h
*/"
"NOTE: The serial buffer size must be larger than 36 + packet size
In this example we use an UDP packet of 48 bytes so the buffer must be
at least 36+48=84 bytes that exceeds the default buffer size (64).
You must modify the serial buffer size to 128
For HardwareSerial modify _SS_MAX_RX_BUFF in
Arduino\hardware\arduino\avr\cores\arduino\SoftwareSerial.h
For SoftwareSerial modify _SS_MAX_RX_BUFF in
Arduino\hardware\arduino\avr\libraries\SoftwareSerial\SoftwareSerial.h
*/"
我是使用軟體序列埠與 ESP8266 溝通, 因此要修改 SoftSerial 的預設緩衝器, 我找到下列檔案中的 _SS_MAX_RX_BUFF 常數, 並將其從 64 改為 128 :
E:\arduino-1.6.1\hardware\arduino\avr\libraries\SoftwareSerial\SoftwareSerial.h
#define _SS_MAX_RX_BUFF 128 // RX buffer size
#ifndef GCC_VERSION
但改了還是無效. Why?
但改了還是無效. Why?
沒有留言 :
張貼留言