# Arduino 液晶顯示器測試
而此次所使用的 Arduino + ESP8266 物聯網模組是上周焊接製作完成的, 將 Nano 與 ESP8266 焊在一個 15*16 洞的小型洞洞板上, 內建一個蜂鳴器與切換開關, 組成一個小型開發板, 專門用在物聯網的應用上. 使用 Nano 而非更小巧的 Pro Mini 的原因是 Nano 具有 Mini USB 插座, 方便應用開發時期程式上傳與偵錯之用, 參考 :
# 製作 Arduino Nano + ESP8266 物聯網模組
也可以按下列電路圖用杜邦線自行接線 :
# 以 Arduino + ESP8266 物聯網模組重作 NTP 實驗 (二)
由於此 Arduino+ESP8266 物聯網開發模組中 Nano 的 D7,D8 被拿來用作 Arduino 軟體序列埠的 Rx 與 Tx; D4 接腳被拿來做為模式選擇輸入; D9 被拿來接蜂鳴器, 這四個腳被保留不能使用, 所以我改用 D10~D13 接腳當作資料輸出, 與 1602 模組的 D4~D7 分別相接.
另外要控制 1602 可被寫入資料, 其第 5 腳 (RW) 須接 GND, 第 4 腳 (RS) 我選擇接到 Nano 的 D2, 第 6 腳 (E) 接到 Nano 的 D3, 詳細接線對照如下表 :
1602 接腳 | 功能 | Arduino Nano 接腳 |
1 (VSS) | 電源負極 | GND |
2 (VCC) | 電源正極 | 5V |
3 (Vo) | 調整對比 | 可變電阻中腳 |
4 (RS) | D0~D7放入資料暫存器 (1) 或指令資料暫存器 (0) | D2 |
5 (RW) | 讀取 (1) 或寫入 (0) LCD | GND (寫入) |
6 (E) | 可寫入 (1) 或不可寫入 (0) LCD | D3 |
7 (D0) | 資料位元 0 | 不接 |
8 (D1) | 資料位元 1 | 不接 |
9 (D2) | 資料位元 2 | 不接 |
10 (D3) | 資料位元 3 | 不接 |
11 (D4) | 資料位元 4 | D10 |
12 (D5) | 資料位元 5 | D11 |
13 (D6) | 資料位元 6 | D12 |
14 (D7) | 資料位元 7 | D13 |
15 (A+) | 背光電源正極 | 5V |
16 (-K) | 背光電源負極 | GND |
這裡面還有一個重點是 1602 的第三腳必須接一個可變電阻的中間接腳來調整亮度對比, 否則液晶會太亮, 就算輸出資料正確也甚麼都看不到, 就只是一片亮亮的藍屏, 我使用的是如下的可變電阻, 剛好可以插在模組板上的 2*4 連通排母上方便接線, 其左腳接地, 右腳接 +5V, 中間腳則接 1602 的第 3 腳 (Vo) :
它上面有一個轉鈕, 可用小的一字起子調整電阻值, 使要顯示的字能在背景光的對比中明確顯示. 我用 Fritzing 繪製了 Nano 與 1602 的接線圖如下 :
其中紅色線為 +5V, 黑色線為 GND, 其餘為信號線. 線路接好後就可以修改程式了, 我把前面實驗二程式中的 setup() 函式的系統部分稍作修改, 多定義了一個 cifsr 字串來接收 AT+CIFSR 指令傳回的回應, 然後在後續的應用部分利用字串處理技巧取出 ESP8266 在區網裡所取得的 IP, 將其顯示在 1602 LCD 的第一列.
其次將原先的 GetTime() 函式改為 getCST(), 它會在取得 NTP 伺服器回應的時戳後, 換算成 UTC 時間, 但是把其中的 hour 部分加上 8, 因為台北時間為中原標準時間 CST 時區 (UTC+8), 最後把傳回的 HH:MM:SS 格式之時間字串顯示在 1602 LCD 的第二列, 完整程式如下 :
#include <SoftwareSerial.h>
#define DEBUG true
//-----application codes listed HERE-----
#include <LiquidCrystal.h>
#define RS 2
#define E 3
#define D4 10
#define D5 11
#define D6 12
#define D7 13
//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 HERE-----
//Request for NTP time stamp (in the first 48 bytes)
byte packetBuffer[128]; //buffer : send & recv data to/from NTP
LiquidCrystal lcd(RS,E,D4,D5,D6,D7); //create LCD object
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);
sendData("AT+GMR\r\n",1000,DEBUG);
if (mode==LOW) { //setup mode : for wifi configuration
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",2000,DEBUG); //turn on server 80 port
}
else { //working mode : for running application
sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as a station
delay(3000); //wait for wifi connection to get local ip
}
String cifsr=sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
//-----application codes listed HERE-----
String ip="0.0.0.0";
if (cifsr.indexOf("OK") != -1) {
cifsr.replace("AT+CIFSR","");
cifsr.replace("\r\n","");
Serial.println(cifsr);
ip=cifsr.substring(1,cifsr.indexOf("OK"));
}
lcd.begin(16,2); //define 2*16 LCD
lcd.clear(); //clear screen
lcd.setCursor(0,0); //move to left top corner
lcd.print(ip); //print Hello World!
}
void loop() {
if (mode==LOW) {setupWifi();}
else { //-----application codes listed bellow-----
Serial.println("Sending request to NTP server ...");
String cst=getCST();
Serial.println(cst);
lcd.setCursor(0,1); //move to (x,y)
lcd.print(cst); //print CST time
}
}
String getCST() {
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"82.209.243.241\",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
unsigned long epoch=secsSince1900 - 2208988800UL;
Serial.print("Unix time stamp (seconds since 1970-01-01)=");
Serial.println(epoch); //print Unix time
//Convert epoch to UTC/GMT (Greenwich Meridian) hour:minute:second
String cst="";
byte hour=(epoch % 86400L) / 3600 + 8; //hour (86400 secs per day)
if (hour > 24) {hour -= 24;}
if (hour < 10) {cst += "0";} //prefix with "0" if single digit
cst.concat(hour);
cst.concat(":");
byte min=(epoch % 3600) / 60; //minute (3600 secs per minute)
if (min < 10) {cst.concat("0");} //prefix with "0" if single digit
cst.concat(min);
cst.concat(":");
byte sec=epoch % 60; //second
if (sec < 10) {cst.concat("0");} //prefix with "0" if single digit
cst.concat(sec);
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
return cst;
}
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;
}
編譯後程式記憶體 (Flash) 耗掉逾四成, 變數記憶體 (SRAM) 耗掉近六成 :
草稿碼使用了 13,210 bytes (43%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 1,198 bytes (58%) 的動態記憶體,剩餘 850 bytes 供局部變數。最大值為 2,048 bytes 。
注意, 在擷取 IP 時發現, ESP8266 回應的 AT+CIFSR 後面應該是跳行 \r\n 才對, 但卻沒辦法用 replace() 去除, 只好暫時在 substring() 中從索引 1 開始取子字串來解決. 也許可用其他可顯示 ASCII 碼的通訊程式來檢查看看. 程式中關於 C 語言字串處理之函數參考 :
# Arduino 基本語法筆記
程式上傳後執行結果如下所示 :
上面影片顯示, 1602 LCD 顯示器上的秒數每次大約跳 8 秒. 可知, 在沒有額外添加 delay() 情況下, 每次向 NTP 伺服器查詢, 大約 8 秒後會收到回應的時戳. 此實驗後續可以做的是搭配 RTC 模組來做時鐘, 這樣秒數就會自然跳動, 然後使用 NTP 伺服器來每隔一段時間對 RTC 進行時間校正. 另外電流表顯示整體耗電平均約 20mA, 最大約 40mA, 還蠻省電的喔.
OK, 我想這次重做 NTP 的實驗到這裡應該可以收工囉, 上面的程式架構也將是未來做實驗的範本. 這兩個星期來的摸索不僅製作了新板子, 也解決了老問題, 可說收穫頗豐. 有了這個連網模組, 以後做實驗就方便多了.
參考資料 :
# EasyIOT
# Sparkfun Thing Hookup Guide
# HTTP slide show on ESP8266 w/ ILI9341 TFT LCD using Arduino IDE
# Building a Clock with an Arduino
你好我想詢問有關ESP8266的部分請問可以加你FB嗎??
回覆刪除OK, 但我很久才會看 FB 一次, 您可以 PO 在這裡喔!
回覆刪除因為我的專題是想要,把資料透過ESP8266這塊模組,使用WIFI直接上傳到GOOGLE SPREADSHEET上,但是我一直有問題所以想問你有什麼方式可以教我嗎??
回覆刪除你好我的專題目的,是要使用ESP8266上傳資料到google spreadsheet上,想請問這方面你有碰觸過嗎?
回覆刪除並我想問你,碰觸Arduino多久了?
Sorry, 我沒試過耶! 我都將資料丟到物聯網伺服器, 不過丟到 google 雲端硬碟是個好主意, 您可參考這篇 :
回覆刪除http://www.instructables.com/id/Post-to-Google-Docs-with-Arduino/
有空我也來試試.