經過兩個月來的摸索, 對於 Arduino 經由 ESP8266 連上網路的實驗終於來到實作階段. 這當中也分心去研究如何製做 ESP8266 的轉接板, 雖已經有腹案, 但覺得還是先把連網實驗做完再來搞定轉接板好了. 這兩塊板子互連主要有兩個障礙, 一是開發階段串列埠不夠的問題, 二是 Arduino 的 5V 位準與 ESP8266 的 3.3V 位準轉換的問題.
不管是 UNO 還是 Nano, 它們都只有一組 UART 串列埠 TX0 與 RX0, 而且與 USB 是共接的, 亦即如果將 Arduino 的 USB 接到電腦用來上傳程式與透過 Serial.print() 來除錯與追蹤執行結果, 那麼串列埠 TX0/RX0 就不能同時接到 ESP8266 來傳送 AT 指令與接收回應.
解決之道是使用 SoftwareSerial 函式庫來將一般 DIO 針腳模擬成軟體串列埠, 我習慣是使用 Pin 11 當 TX, Pin 10 當 RX, 參考之前寫的 UART 串列埠測試 :
#
Arduino 串列埠測試 (UART)
第二個問題是 Arduino 與 ESP8266 互連時電壓位準的調適. ESP8266 工作於 3.3V 電源下, 不可以直接將 Ardino 的 TX 接到 ESP8266 的 URXD 腳 (即 RX), 這麼做會讓 ESP8266 陣亡, 參考 ESP8266 規格 :
#
https://nurdspace.nl/images/e/e0/ESP8266_Specifications_English.pdf
在這本規格書的第 15 頁, 談到其 DIO 針腳的電氣規格, 最大輸入電壓是 3.6V (Input high voltage, maximum), 與 Arduino 的 5V 有 1.4V 壓差, 這可能將 URXD 針腳與內部電路燒毀. 而其 DIO 腳高邏輯的最低輸出電壓為 0.8*VIO (Output high voltage)=0.8*3.3=2.64V, 這在 TTL 會被視為 High (>2V), 所以直接推動 Arduino 的 RX 腳基本上沒問題 (只是邊限小了些, 只有約 0.64V). 若要可靠些, 可用 MOSFET 2N7000 與兩顆電阻做成 Level shifter 來轉成 5V 系統, 參考 :
#
用 MOFFET 2N7000 做 5V 與 3.3V 位準轉換
解決了以上這兩個問題, 就可以進行互連實驗了. 這實驗主要是參考 Proteus 君這篇大作 :
#
初遇 IoT ( Internet of Thing, 物聯網 ) - 使用 Arduino 控制 ESP8266 無線模組上傳資料到 IoT Server ( ThingSpeak )
在這篇文章中, Proteus 使用 UNO 做為主控板, 而我是用小巧方便的 Nano. 他使用了一塊位準轉換模組進行 TX/RX 的 5V/3.3V 轉換, 我則參考底下這篇文章,
採用簡單的 10K+20K 電阻分壓法 (註 :
應該使用 1K+2K) 將 Arduino 的 TX 介接到 ESP8266 的 URXD 腳, RX 則直接與 ESP8266 的 UXTD 腳相連 :
#
A Simple IoT Project with the ESP8266 WiFi module
此作者使用 3 個 1K 電阻來分壓,
其實用 10K+20K 也無妨 (註 :
會不穩), 因為 Arduino 的 DIO 腳當輸入時, 輸入阻抗約為 100M Ohms, 還蠻大的, 參考 :
#
Properties of Pins Configured as INPUT
電路圖如下 (用 Upverter 繪製) :
註 :
實驗發現分壓電阻不能用 10k+20k 組合, RC 效應太大, 應改用 1K+2K 或 220+470 組合.
麵包板接線圖如下 (用 Fritzing 繪製) :
這裡我使用麵包板專用電源供電給 ESP8266 模組 (上方 3.3V) 與 DHT11 (下方 5V), 而 Arduino Nano 則由 PC 的 USB 供電, 這兩組電源的 GND 必須共接地 (Nano 左上方第三隻腳的黑線). 當然 DHT11 吃 Nano 的 5V 也是可以的, 此處接麵包板專用電源是為了接線方便. DHT11 的第二腳 Data 要接一個 10K 上拉電阻到 5V, 其 VCC 則接一個 0.1uF 電容接地.
硬體弄好後, 得去物聯網伺服器網站
Thingspeak 註冊一個帳號, 在 Proteus 的 "
初遇 IoT" 有詳細說明, 不再贅述. Thingspeak 有提供 Arduino 的應用範例, 參考 :
#
Using an Arduino + Ethernet Shield to Update a ThingSpeak Channel
在上面這個教學文中, 有一個重要資料必須記下來, 就是 Thingspeak 伺服器的 IP 位址 :
184.106.153.149, 這在 ESP8266 與 Thingspeak 伺服器建立 TCP 連線時會用到, 若網路中有 DNS 伺服器, 也可以用
api.thingspeak.com 取代.
我將申請的通道 (Channel) 命名為 DHT11 :
按 "Channel Settings" 頁籤可以看到, 每一個 Channel 可以記錄最多 8 個欄位資料, 但在這個實驗中我只用到三個欄位 : 攝氏溫度 Temperature(degC), 華式溫度 Temperature(degF), 與濕度 humidity(%) :
設定好欄位後要按底下的 "Save Chanel" 鈕才會真的存進去 :
再切到 API Keys, 裡面有 Read 與 Write 兩種金鑰, Read Key 用來從網路程式讀取 Thingspeak 通道資料, 而 Write Key 用來寫入資料, Arduino 要將取得的溫溼度寫到 Thingspeak 伺服器需要的是 Write Key, 這要記下來程式中要用到 :
這樣我們就可以在 Arduino 程式完成之前, 先用 HTTP 的 GET 方式傳送資料給 Thingspeak 伺服器, 測試看看這個寫入金鑰管不管用, 其格式為 :
http://api.thingspeak.com/update?
api_key=[寫入金鑰]&field1=[攝氏溫度]&field2=[華氏溫度]&field3=[濕度]
有些網站 (例如
Proteus) 使用的金鑰名稱是 key 而非 api_key, 其實我測試的結果是兩個都可以, Thingspeak 都接受 :
http://api.thingspeak.com/update?
key=[寫入金鑰]&field1=[攝氏溫度]&field2=[華氏溫度]&field3=[濕度]
例如 :
http://api.thingspeak.com/update?api_key=NO5N8C7T2KINFCQE&field1=25.4&field2=87.4&field3=55
把此網址貼到瀏覽器,
會回應一個數字, 表示這是第幾筆資料了. 例如 4 表示剛剛寫進去的是第四筆資料, 這時候在 Thingspeak 網站上切換至 Public View 或 Private View 就可以看到填入的數據隨時間變化的圖形了 :
如果要清除這些測試的資料, 按 "Channel Settings" 頁籤底下的 "Clear Channel" 按鈕即刪除.
最後是 Arduino 程式部分, 為了簡化流程, 我不打算在 Arduino 程式中處理 ESP8266 連線無線基地台的 AT 指令, 而是預先將 ESP8266 以 PL2303HX 之類的 TTL 轉 USB 轉換線連接電腦 USB 埠, 用 Realterm 等軟體將 ESP8266 的工作模式設定成 STA 模式與單一連接, AT 指令如下 :
AT+CWMODE=1 (STA 模式)
其次, ESP8266 不是當 Server, 因此只需要單一 TCP 連線 :
AT+CIPMUX=0 (單一連線)
建立 TCP設定要連接的無線基地台 (SSID, PASSWORD), 其 AT 指令如下 :
AT+CWJAP="EDIMAX-tony","123456789111"
設定完後 ESP8266 會記錄這些設定在 Flash 中, 即使關掉電源也會記住, 只要一通電就會尋找原先設定的基地台自動連線. 參考 :
設定好之後, 我們先用 Realterm 直接對 ESP8266 下 AT 指令來模擬傳送溫溼度紀錄到 Thingspeak 伺服器的動作.
首先須與 Thingspeak 伺服器的 80 埠建立 TCP 連線, AT 指令如下 :
AT+CIPSTART="TCP","184.106.153.149",80
其中 184.106.153.149 乃是 Thingspeak 伺服器的 IP 位址. 連線成功的話, ESP8266 會回應 :
OK
Linked
表示已經建立 TCP 連線了.
其次是要告訴 ESP8266 我們打算要傳送多少字元的資料, 以便其準備所需之緩衝區. 所使用的 AT 指令是
CIPSEND, 參考 ESP8266 快速指南中的第八頁 "Acting as a TCP Client" :
#
ESP8266 WiFi Module Quick Start Guide (PDF)
不過我們要做些調整, 此文首先是將 CIPMUX 設為 1 (允許多重連線, 預設為 0, 單一連線), 但這是不需要的, 因為我們只是要用到
ESP8266 的 STA 模式而已, 不需要作為伺服器時之多重連線. 也因此, CIPSTART 指令也不需要指定 id, 參考 :
#
ESP8266 AT Command Set (PDF)
當然, 要告知資料長度, 得先知道要傳送的是啥資料. 我們在上面測試 Thingspeak 伺服器時, 是在瀏覽器貼上 Thingspeak API 的網址, 但在作為客戶端 (STA 模式) 的 ESP8266 而言, 則是要向目的主機發出如下格式之 GET 請求 :
GET /目的網頁網址
參考 :
#
https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
而此處目的網頁就是 Thingspeak 的更新通道資料的網頁程式 update :
update?api_key=寫入金鑰&欄位1=數值1&欄位2=數值2&欄位3=數值3....
我們要向 ESP8266 傳送的資料格式如下 :
GET /update?api_key=NO5N8C7T2KINFCQE&field1=29.00&field2=84.20&field3=73.00
將此 GET 指令貼到 WORD 中進行字數統計, 可知含空格 (GET 後面那個) 共 75 字元 :
但我們用 Realterm 傳送 AT 指令給 ESP8266 時都必須在後面加上 \r\n 這兩個跳行字元, 因此全部字元長度須再加上 2, 總共是 75+2=77, 因此 AT 指令如下 :
AT+CIPSEND=77
參考 :
#
Tutorial: IoT Datalogger with ESP8266 WiFi Module and FRDM-KL25Z
注意,
這個總字元長度必須恰恰好, 多跟少都不行, ESP8266 會因為兜不攏而當在那邊, 無法完成傳送任務. 此 AT 指令下完後, ESP8266 會回應一個大於提示號, 表示可以開始傳送資料 :
>
最後是就是把要溫溼度資料的 GET 指令字串貼到 > 後面傳送 :
>
GET /update?api_key=NO5N8C7T2KINFCQE&field1=24.00&field2=83.20&field3=77.00
成功的話, ESP8266 立刻回應 +IPD 如下 :
SEND OK
+IPD,1:7
OK
OK
Unlink
傳送完資料 ESP8266 就自動拆除 TCP 連線, 顯示 Unlink. 如果是沒有, 必須手動拆線, 其 AT 指令是 AT+CIPCLOSE. 注意, 上面傳送完成後, ESP8266 會向 Arduino 回送 +IPD, 後面的 1 表示伺服器回應資料的長度, 此處 1 表示 1 個 byte, 冒號後面 7 即 Thingspeak 伺服器回應的資料為 7, 表示此為第七筆寫入之資料, 參考 :
#
https://github.com/espressif/esp8266_at/wiki/IPD
這樣便確認 ESP8266 可以正確透過 WiFi 基地台向 Thingspeak 伺服器傳送更新數據了.
完成以上測試後, 便可將 PL2303HX 轉換線移除, 將 ESP8266 與 Arduino 照上面電路圖連接. 然後撰寫 Arduino 程式, 模擬上面的人工操作, 自動向 ESP8266 傳送 AT 指令, 使其透過 WiFi 連線向 Thingspeak 伺服器更新溫溼度資料.
摘要以上四個步驟與 Arduino 程式作法 :
- 與目的主機建立 TCP 連線 :
AT+CIPSTART="TCP","184.106.153.149",80 //連線 Thingspeak 主機
在 Arduino 中以字串表示時, 須將雙引號以倒斜線跳開 :
String cmd="AT+CIPSTART=\"TCP\",\"184.106.153.149\",80";
- 傳送 GET 字串長度給 ESP8266 :
AT+CIPSEND=GET 字串長度
- 傳送 GET 字串給 ESP8266 :
sSerial.println(GET 字串)
- 關閉 TCP 連線 :
AT+CIPCLOSE
下面的測試程式是我參考阿喵就像家的 "
ESP8266 Workshop" 投影片中的程式修改來模擬上面 Realterm 傳送 AT 指令, 用來驗證串列埠與軟體串列埠接線是否正常運作 :
#include <SoftwareSerial.h>
SoftwareSerial sSerial(10,11); //(RX,TX) 與 ESP8266 介接的軟體串列埠
void setup() {
sSerial.begin(9600);
Serial.begin(9600);
Serial.println("SoftSerial to ESP8266 AT commands test ...");
}
void loop() {
if (sSerial.available()) { //若軟體串列埠 RX 有收到來自 ESP8266 的回應字元
Serial.write(sSerial.read()); //在串列埠監控視窗顯示 ESP8266 的回應字元
}
if (Serial.available()) { //若串列埠 RX 有收到來自 PC 的 AT 指令字元 (USB TX)
char c=Serial.read(); //先暫存在字元變數 c 緩衝一下
sSerial.write(c); //將 PC 的傳來的字元傳給 ESP8266
}
delay(50); //緩衝 50ms 以免傳送太快 (不用, 後述)
}
程式上傳後, 打開序列埠監視視窗, 先把右下方的行尾加送模式選為 "NL & CR", 表示每次傳送時都要在傳送字串後面加上跳行, 然後在上方輸入框敲入 AT 指令, 按傳送, 若接線沒問題, 應可在下方框看到 ESP8266 的回應訊息 :
回應訊息如下 :
SoftSerial to ESP8266 test...
AT
OK
AT+RST
OK
bB��@�Sc��S��"�JL��������H�
[System Ready, Vendor:www.ai-thinker.com
AT+CIPSTART="TCP","184.106.153.149",80
OK
Linked
AT+CIPSEND=77
> GET /update?api_key=NO5N8C7T2KINFCQE&field1=25.00&field2=84.20&field3=78.00
SEND OK
+IPD,1:8
OK
OK
Unlink
可見接線沒問題, 電壓位準也 OK, 整個架構是正確的. 嘿! 我突然發現, 利用上面這個程式我不需要 USB-TTL 轉接線, 用 Arduino 就可以對 ESP8266 下 AT 指令了耶! 這真是太棒了!
不過要注意的是, 上面程式最後一行的 delay(50) 是需要的, 如果取消就似乎太快, 序列部監視視窗根本毫無反應. 我試過大約 40~50 ms 最適當, 但偶而還是會有幾個字元傳送錯誤, 要多試幾次. 如果毫無反應, 可能是接線不良, 特別是 ESP8266 的 TX 到 Arduino 的 RX 這條. 若 AT 指令下下去後, ESP8266 的藍燈沒閃, 表示沒收到信號, 要檢查 Arduino TX 到 ESP8266 RX 這條線. (此 delay 完全不需要, 原因後述)
以上述程式驗證連線無誤, 軟體序列埠與硬體序列埠可正常通訊後, 最後以 "
A Simple IoT Project with the ESP8266 WiFi module" 這篇文章的程式為主軸, 綜合之前做 DHT11 溫溼度測試 (參考
Arduino 溫濕度感測器 DHT11 測試), 改寫為如下程式, 由 Arduino 讀取 DHT11 量測之溫濕度數據, 透過軟體序列埠傳送給 ESP8266 經 WiFi 網路給 Thingspeak 伺服器.
這裡用到的 DHT11 函式庫可至
Adafruit 網站下載, 按網頁右下角的
"Download ZIP" 即可.
注意, Arduino IDE 必須關閉重開才會去抓函式庫, 否則仍編譯失敗.
程式如下 :
#include <SoftwareSerial.h> //載入軟體串列埠函式庫
#include <DHT.h> //載入 DHT11 函式庫
#define DHTPIN 2 //定義 DIO 腳 2 為 DHT11 輸入
#define DHTTYPE DHT11 //定義 DHT 型態為 DHT11
DHT dht(DHTPIN, DHTTYPE); //初始化 DHT11 感測器
int LED=13; //宣告 DIO 腳 13 為 LED 輸出
String api_key="NO5N8C7T2KINFCQE"; //Thingspeak API Write Key
SoftwareSerial sSerial(10,11); //設定軟體串列埠腳位 RX, TX為 DIO 腳 10, 11
void setup() {
pinMode(LED, OUTPUT); //設定 DIO Pin 13 為 LED 輸出
Serial.begin(9600); //啟始硬體串列埠 (除錯用)
sSerial.begin(9600); //啟始軟體串列埠 (與 ESP8266 介接用)
dht.begin(); //啟始 DHT11 溫濕度感測器
sSerial.println("AT+RST"); //軟體串列埠傳送 AT 指令重啟 ESP8266
}
void loop() {
float h=dht.readHumidity(); //讀取濕度
float c=dht.readTemperature(); //讀取攝氏溫度
float f=dht.readTemperature(true); //讀取華氏溫度
//有任何一個是 NAN 就不往下執行資料傳送
if (isnan(h) || isnan(c) || isnan(f)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
blink_led(1000,500); //進入資料傳送程序 : LED 閃爍一次
String param="&field1=" + (String)c + "&field2=" + (String)f +
"&field3=" + (String)h; //製作參數字串
//Serial.println(param); //輸出字串參數於監控視窗
//與 Thingspeak 主機建立 TCP 連線
String cmd="AT+CIPSTART=\"TCP\",\"184.106.153.149\",80";
sSerial.println(cmd); //向 ESP8266 傳送 TCP 連線之 AT 指令
//偵測 TCP 連線是否成功
if (sSerial.find("Error")) {
Serial.println("AT+CIPSTART error!");
return; //連線失敗跳出目前迴圈 (不做後續傳送作業)
}
Serial.println(cmd); //輸出 AT 指令於監控視窗
//製作 GET 字串
String GET="GET /update?api_key=" + api_key + param + "\r\n\r\n";
Serial.println(GET); //顯示 GET 字串內容於監控視窗
cmd="AT+CIPSEND=" + String(GET.length()); //傳送 GET 字串長度之 AT 指令
sSerial.println(cmd); //告知 ESP8266 即將傳送之 GET 字串長度
Serial.println(cmd); //輸出 AT 指令於監控視窗
//檢查 ESP8266 是否回應
if (sSerial.find(">")) { //若收到 ESP8266 的回應標頭結束字元
sSerial.print(GET); //向 ESP8266 傳送 GET 字串內容
}
else { //沒有收到 ESP8266 回應
sSerial.println("AT+CIPCLOSE"); //關閉 TCP 連線
Serial.println("AT+CIPCLOSE"); //顯示連線關閉訊息於監控視窗
}
delay(16000); //延遲 16 秒 (因 Thingspeak 每次更新須隔 15 秒)
}
void blink_led(int on, int off) { //LED 閃爍函式
digitalWrite(LED, HIGH);
delay(on);
digitalWrite(LED, LOW);
delay(off);
}
結果不會動, 我把 Thingspeak 的通道清除後, 資料仍然沒進來, 空空如也.
Why? 程式來回檢查好幾遍了, 沒錯呀, 跟原作者的做法相同啊? 而且每 16 秒鐘的 loop 都有看到 ESP8266 的藍燈在閃, 表示有傳送資料, 為何 Thingspeak 沒收到呢?
序列埠監視視窗的輸出如下 :
AT+CIPSTART="TCP","184.106.153.149",80
GET /update?api_key=NO5N8C7T2KINFCQE&field1=28.00&field2=82.40&field3=81.00
AT+CIPSEND=79
AT+CIPCLOSE
可見 GET 指令沒有送出去. .再研究 ....
**********************************************
晚上回高雄後仍不死心, 又重新測試一遍, 結果更糟, 連前面利用序列埠監視視窗對 ESP8266 下 AT 指令的程式也沒反應, 接線稍微喬一喬有改善, 但仍然會遺失字元, 導致 Error. 難道麵包板接線這麼脆弱嗎? 移動一下就不行? 我突然想到這是電訊號問題, 該不會是 10K+20K 分壓電阻太大了吧? 這是唯一跟原作者不一樣的地方.
之前就有懷疑這點, 上周五下班後特地跑去禾樺買了一包 1K+2K 的電阻. 於是拿出來替換掉 10+20k 分壓組合, 而且去掉我自己加的很奇怪的 delay(50), 果然! 序列埠監視視窗傳送 AT 指令不再掉字元, 非常順啊! 原來搞了我兩三天的就是分壓電阻! 我想原因可能是, 雖然 10K/20K 對 100M 的輸入阻抗不算甚麼, 但是,
電阻越高, RC 反應時間變長, 可能使訊號變形, 導致字元錯誤或遺失 (有示波器的話應該就能看得明白了). 電路學中, RC 電路的時間常數 R*C 表示電壓上升至終值得 65% 的時間, 電阻越大時間常數也越大, 當然 High 可能被認為是 Low 而產生訊息錯誤了. 參考 :
#
RC電路的時間常數
原作者使用 1K 電阻是對的, 我自作聰明換成 10K 根本是自找苦吃.
找到原因後, 再重新上傳 DHT11 溫溼度測量程式, 哇, 終於成功啦 :
圖中溫度突然上升, 濕度突然下降是用吹風機熱風吹的緣故. 這個 Thingspeak 曲線圖會自動更新, 不需要手動重新整理網頁, 蠻不錯的.
參考 :
#
ESP8266 Community Forum
#
ESP8266 WiFi module and 5V Arduino connection
#
ESP8266 wiring with Arduino
#
ESP8266 wiring schemas
#
深入淺出 Wifi 晶片 ESP8266 with Arduino
#
SoftwareSerial Library
#
A Simple IoT Project with the ESP8266 WiFi module
#
Programming an Arduino via WiFi with the ESP8266
#
http://www.arduinesp.com/thingspeak (使用 ESP8266WiFi.h)
#
ESP8266 + Arduino Uno: Can't send AT Commands
#
How to use ESP8266 ESP-01 as a SENSOR web client
#
ESP8266 Wi-Fi Module with Teensy and Arduino
#
Freaky response from the ESP8266 with Arduino Nano
#
Programming an Arduino via WiFi with the ESP8266
#
ThingSpeak-Arduino-Examples (Ethernet)
#
Using an Arduino + Ethernet Shield to Update a ThingSpeak Channel
#
http://electronut.in/posts/ (孟加拉)
#
Arduino 結合ESP8266將空氣資料送到Synology NAS(取代Thingspeak)
#
UPLOAD SENSORS DATA TO THINGSPEAK BY USING ARDUINO AND ESP8266
關於 AT 指令的進一步探討, 參考 :
#
Wifi success, for a change!
另外, 在 "
A Simple IoT Project with the ESP8266 WiFi module" 這篇文章裡, 作者使用了一個 C 函數 dtstrf() 來將計算所得之溫度浮點數轉成字串, 剛開始時對這怪異的函數名稱很感冒 (意思是 double to string function), 後來在下面這篇看到其用法解釋, 發現還真好用呢 !
#
Displaying FLOAT variables in Arduino
其參數與用法簡單說明如下 :
dtstrf(浮點數, 總字元數, 小數點後位數, 字元陣列)
其中, 總字元數轉換為字串後含小數點總共有多少字元, 故 (總字元數-小數點後位數-1) 就是整數部分的位數. 故要先宣告一個字元陣列來儲存轉換後的字串, 其長度須比總字元數長. 例如 d=123.4567 經 dtstrf(d, 5, 1, s) 轉換後, 得到字串 s=123.4.
此作者有一個進階版的溫度資料收集器, 特殊之處在利用 CH_PD 來省電, 在需要傳送資料時將 CH_PD 設為 High, 傳送完畢設為 Low :
#
An IoT Temperature Monitor for my Balcony Garden
參考 :
#
Tutorial: IoT Datalogger with ESP8266 WiFi Module and FRDM-KL25Z
#
teos0009/
ESP8266 Arduino Mega