2015年11月9日 星期一

ITEAD WeeESP8266 函式庫測試

在利用 WeeESP8266 函式庫完成與 NTP 伺服器的網路對時實驗後, 繼續對此函式庫的主要函式進行功能測試. 我原先是將所有測試寫在一個程式檔案裡, 但編譯後發現程式超過15KB (約 49%), 動態記憶體使用率超過 75%, 出現如下警語:

"Low memory available, stability problems may occur."

程式執行出現錯誤, 但下 AT 指令又 OK, 這是所謂的 Memory leak 現象嗎? 將程式分成兩半, 執行起來就正常了, 難道此函式庫耗記憶體?

關於 WeeESP8266 函式庫, 參考 :

https://github.com/itead/ITEADLIB_Arduino_WeeESP8266
http://docs.iteadstudio.com/ITEADLIB_Arduino_WeeESP8266/index.html (API)

下面測試 1 用來測試基本函數與工作模式 :

測試 1 : WeeESP8266_test_1.ino

#include "ESP8266.h"
#include <SoftwareSerial.h>

SoftwareSerial sSerial(10, 11);  //(RX:D10, TX:D11)
ESP8266 wifi(sSerial);  //建立名為 wifi 的 ESP8266 物件

void setup() {
  Serial.begin(9600);  //啟始硬體序列埠 (debug 用)  
  Serial.println("*** WeeESP8266 WiFi Library Function Test ***");
  if (wifi.kick()) {Serial.println("Reset ESP8266 ... OK");}  //重啟 ESP8266
  else {Serial.println("Reset ESP8266 ... Error");} 
  Serial.println("Firmware Version ... " + wifi.getVersion()); //傳回 AT 韌體版本  
  Serial.println("Local IP ... " + wifi.getLocalIP()); //傳回本地 IP
  //測試工作模式
  if (wifi.setOprToSoftAP()) {Serial.println("Set Operation Mode=AP ... OK");}
  else {Serial.println("Set Operation Mode=AP ... Error");}  //設為 AP 模式 (2)
  delay(3000);
  Serial.println("Local IP ... " + wifi.getLocalIP()); //傳回本地 IP 
  if (wifi.setOprToStationSoftAP()) {Serial.println("Set Operation Mode=Station+AP ... OK");} 
  else {Serial.println("Set Operation Mode=Station+AP ... Error");}  //設為 STA+AP 模式 (3)
  delay(5000); //等待 DHCP 指派 IP
  Serial.println("Local IP ... " + wifi.getLocalIP()); //傳回本地 IP 
  if (wifi.setOprToStation()) {Serial.println("Set Operation Mode=Station ... OK");} 
  else {Serial.println("Set Operation Mode=Station ... Error");}  //設為 STA 模式 (1)
  delay(3000); //等待 DHCP 指派 IP
  Serial.println("Local IP ... " + wifi.getLocalIP()); //傳回本地 IP
  Serial.print("List available APs ... "); 
  Serial.println(wifi.getAPList().c_str()); //傳回附近可用的 AP   
  if (wifi.restart()) {Serial.println("Restart ESP8266 ... OK");}  //重啟 ESP8266
  else {Serial.println("Restart ESP8266 ... Error");} 
  }
 
void loop() {
  if (sSerial.available()) {   //若軟體序列埠 Rx 收到資料 (來自 ESP8266)
    Serial.write(sSerial.read());  //讀取後寫入硬體序列埠 Tx (PC)
    }
  if (Serial.available()) {  //若硬體序列埠 Rx 收到資料 (來自 PC)
    sSerial.write(Serial.read());  //讀取後寫入軟體序列埠 Tx (ESP8266)
    }  
  }

此程式編譯後佔 34% Flash 記憶體, 52% SRAM 記憶體. 其輸出如下 :

*** WeeESP8266 WiFi Library Function Test ***
Reset ESP8266 ... OK
Firmware Version ... 0018000902
Local IP ... 192.168.2.106             (模式 1 由 DHCP 指派之 IP)
Set Operation Mode=AP ... OK
Local IP ... 192.168.4.1                 (模式 2 基地台 IP)
Set Operation Mode=Station+AP ... OK
Local IP ... 192.168.4.1                 (模式 3 有兩個 IP)
192.168.2.106
Set Operation Mode=Station ... OK
Local IP ... 192.168.2.106             (模式 1 由 DHCP 指派之 IP)
List available APs ...
Restart ESP8266 ... OK

從輸出可知, getAPList() 函數不知何故竟然沒有傳回可用 AP 字串, 我檢查原始碼, 其 AT 指令沒問題, 可能是接收回應的函數有 bug.

下面的程式是在單一連線模式下測試 UDP 之註冊/登出, 以及 TCP 連線之建立與拆線.

測試 2 : WeeESP8266_test_2.ino 

#include "ESP8266.h"
#include <SoftwareSerial.h>

SoftwareSerial sSerial(10, 11);  //(RX:D10, TX:D11)
ESP8266 wifi(sSerial);  //建立名為 wifi 的 ESP8266 物件

void setup() {
  Serial.begin(9600);  //啟始硬體序列埠 (debug 用) 
  Serial.println("*** WeeESP8266 WiFi Library Function Test ***");
  //設定為單一連線模式
  if (wifi.disableMUX()) {Serial.println("disable Mux (single connection) ... OK");}
  else {Serial.println("disable Mux (single connection) ... Error");} 
  //測試 UDP 註冊與登出
  if (wifi.registerUDP("82.209.243.241", 123)) { //測試 NTP 伺服器
    Serial.println("Register UDP ... OK");
    Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
    if (wifi.unregisterUDP()) {  //登出
      Serial.println("Unregister UDP ... OK");
      Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
      }
    else {
      Serial.println("Unregister UDP ... Error");
      }
    }
  else {Serial.println("Register UDP ... Error");}   
  //測試 TCP 連線
  uint8_t buf[128]={0};  //儲存回應字元的緩衝器
  if (wifi.createTCP("www.google.com", 80)) {
    Serial.println("Create TCP connection (www.google.com) ... OK");
    Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
    char *data = "GET /\r\n";  //字元陣列指標, 後面一定要加跳行 \r\n
    wifi.send((const uint8_t*)data, strlen(data));  //傳送資料
    uint32_t len=wifi.recv(buf, sizeof(buf), 10000);  //接收回應字元 (10 秒逾時)
    if (len > 0) {  //有收到回應
      Serial.print("Received:[");
      for (uint32_t i = 0; i < len; i++) {  //輸出回應字元
        Serial.print((char)buf[i]);
        }
      Serial.println("]");
      }
    if (wifi.releaseTCP()) {Serial.println("Release TCP ... OK");}  //關閉連線
    else {Serial.println("Release TCP ... Error");}     
    }
  else {Serial.println("Create TCP connection (www.google.com) ... Error");}
  }
 
void loop() {}

注意, 傳送字串必須放在字元陣列指標所指之位址, 而且必須在末尾加上跳行字元 "\r\n". 此程式編譯後佔 Flash 記憶體 37%, 變數佔 SRAM 52%. 其輸出為 :

*** WeeESP8266 WiFi Library Function Test ***
disable Mux (single connection) ... OK   (單一連線)
Register UDP ... OK          (註冊 UDP)
IP Status ... STATUS:4     (Disconnected)
+CIPSTATUS:0,"UDP","82.209.243.241",123,0
Unregister UDP ... OK       (解除註冊 UDP)
IP Status ... STATUS:4      (Disconnected)
Create TCP connection (www.google.com) ... OK   (建立 TCP 連線)
IP Status ... STATUS:3      (Connected)
+CIPSTATUS:0,"TCP","74.125.204.106",80,0    (連線 Google 首頁)
Received:[HTTP/1.0 302 Found    
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Location: http://www.google.com.tw/?gfe_rd=c
]
Release TCP ... OK     (拆除 TCP 連線)

可見在單一連線時, UDP 註冊與 TCP 連線均正常完成.

若設為多重連線模式, 則用 UDP 註冊與 TCP 連線均會失敗 (這是因為沒有指定通道的緣故, 詳後述), 如測試 3 所示 :

測試 3 : WeeESP8266_test_3.ino 

#include "ESP8266.h"
#include <SoftwareSerial.h>

SoftwareSerial sSerial(10, 11);  //(RX:D10, TX:D11)
ESP8266 wifi(sSerial);  //建立名為 wifi 的 ESP8266 物件

void setup() {
  Serial.begin(9600);  //啟始硬體序列埠 (debug 用)  
  Serial.println("*** WeeESP8266 WiFi Library Function Test ***");
  //設定為多重連線模式
  if (wifi.enableMUX()) {Serial.println("Enable Mux (multi connection) ... OK");} 
  else {Serial.println("Enable Mux (multi connection) ... Error");}
  
  //測試 UDP 註冊與登出
  if (wifi.registerUDP("82.209.243.241", 123)) { //測試 NTP 伺服器
    Serial.println("Register UDP ... OK");
    Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
    if (wifi.unregisterUDP()) {
      Serial.println("Unregister UDP ... OK");
      Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
      }
    else {
      Serial.println("Unregister UDP ... Error");
      }
    }
  else {Serial.println("Register UDP ... Error");}    
  //測試 TCP 連線
  uint8_t buf[128]={0};  //儲存回應字元的緩衝器
  if (wifi.createTCP("www.google.com", 80)) {
    Serial.println("Create TCP connection (www.google.com) ... OK");
    Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
    char *data = "GET /\r\n";  //後面一定要加跳行 \r\n
    wifi.send((const uint8_t*)data, strlen(data));  //傳送資料
    uint32_t len=wifi.recv(buf, sizeof(buf), 10000);  //接收回應字元
    if (len > 0) {  //有收到回應
      Serial.print("Received:[");
      for (uint32_t i = 0; i < len; i++) {  //輸出回應字元
        Serial.print((char)buf[i]);
        }
      Serial.println("]");
      }
    if (wifi.releaseTCP()) {Serial.println("Release TCP ... OK");}  //關閉連線
    else {Serial.println("Release TCP ... Error");}      
    } 
  else {Serial.println("Create TCP connection (www.google.com) ... Error");}
  }

其輸出如下 :

*** WeeESP8266 WiFi Library Function Test ***
Enable Mux (multi connection) ... OK
Register UDP ... Error
Create TCP connection (www.google.com) ... Error

可見與我之前的測試結果一樣, 在多重連線時, 起始 UDP/TCP 通訊會失敗. 參見 :

ESP8266 0.9.2.2 版韌體多重連線時 TCP 連線失敗問題

但 ... 仔細看一下函數列表下方, registerUDP(), unregisterUDP(), createTCP(), 以及 releaseTCP() 這四個函數都有單一連線與多重連線兩種多型, 突然發現之前的測試有盲點, 沒注意到 UDP 與 TCP 的傳送, 其 AT 指令在多重連線時必須多指定一個參數 : 通道 id (0~4). 下列測試 4 中, 加了一個通道參數後就成功了 :

測試 4 : WeeESP8266_test_4.ino

#include "ESP8266.h"
#include <SoftwareSerial.h>

SoftwareSerial sSerial(10, 11);  //(RX:D10, TX:D11)
ESP8266 wifi(sSerial);  //建立名為 wifi 的 ESP8266 物件

void setup() {
  Serial.begin(9600);  //啟始硬體序列埠 (debug 用)  
  Serial.println("*** WeeESP8266 WiFi Library Function Test ***");
  //設定為多重連線模式
  if (wifi.enableMUX()) {Serial.println("Enable Mux (multi connection) ... OK");} 
  else {Serial.println("Enable Mux (multi connection) ... Error");}  
  //測試 UDP 註冊與登出
  if (wifi.registerUDP(0,"82.209.243.241", 123)) { //測試 NTP 伺服器
    Serial.println("Register UDP ... OK");
    Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
    if (wifi.unregisterUDP(0)) {
      Serial.println("Unregister UDP ... OK");
      Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
      }
    else {
      Serial.println("Unregister UDP ... Error");
      }
    }
  else {Serial.println("Register UDP ... Error");}    
  //測試 TCP 連線
  uint8_t buf[128]={0};  //儲存回應字元的緩衝器
  if (wifi.createTCP(0,"www.google.com", 80)) {
    Serial.println("Create TCP connection (www.google.com) ... OK");
    Serial.println("IP Status ... " + wifi.getIPStatus()); //傳回目前 IP 連線狀態
    char *data = "GET /\r\n";  //後面一定要加跳行 \r\n
    wifi.send((const uint8_t*)data, strlen(data));  //傳送資料
    uint32_t len=wifi.recv(buf, sizeof(buf), 10000);  //接收回應字元
    if (len > 0) {  //有收到回應
      Serial.print("Received:[");
      for (uint32_t i = 0; i < len; i++) {  //輸出回應字元
        Serial.print((char)buf[i]);
        }
      Serial.println("]");
      }
    if (wifi.releaseTCP(0)) {Serial.println("Release TCP ... OK");}  //關閉連線
    else {Serial.println("Release TCP ... Error");}      
    } 
  else {Serial.println("Create TCP connection (www.google.com) ... Error");}
  }
 
void loop() {
  if (sSerial.available()) {  //若軟體串列埠 RX 有收到來自 ESP8266 的回應字元
    Serial.write(sSerial.read());  //在串列埠監控視窗顯示 ESP8266 的回應字元
    }
  if (Serial.available()) {  //若串列埠 RX 有收到來自 PC 的 AT 指令字元 (USB TX)
    sSerial.write(Serial.read());  //將 PC 的傳來的字元傳給 ESP8266
    } 
  }

此處我指定了多重連線的通道 0, ESP8266 的連線通道最多 5 個, 可任意指定 0 ~ 4, 但關閉時也必須指定通道, 否則會失敗. 關於多重連線的通道, 參考 CIPSTATUS 與 CIPSTART 指令 :

# https://github.com/espressif/esp8266_at/wiki/CIPSTATUS
# https://github.com/espressif/ESP8266_AT/wiki/CIPSTART 

輸出如下 :

*** WeeESP8266 WiFi Library Function Test ***
Enable Mux (multi connection) ... Error  (已經是多重連線, 再設定就會 Error)
Register UDP ... OK
IP Status ... STATUS:3
+CIPSTATUS:0,"UDP","82.209.243.241",123,0
+CIPSTATUS:1,"TCP","64.233.188.104",80,0
Unregister UDP ... OK
IP Status ... STATUS:3
+CIPSTATUS:1,"TCP","64.233.188.104",80,0
Create TCP connection (www.google.com) ... OK
IP Status ... STATUS:3
+CIPSTATUS:0,"TCP","173.194.72.105",80,0
+CIPSTATUS:1,"TCP","64.233.188.104",80,0
Release TCP ... OK

終於搞定了! 多年來寫程式的經驗告訴我, 電腦絕對不會錯, 錯的通常是人腦啦. 我同時也修改了自己寫的函式庫.

2015-11-09 補充 :

今天把測試 1 的程式刪減到只呼叫 3 個函數, 結果 getAPList() 就有回應了, 程式如下 :

#include "ESP8266.h"
#include <SoftwareSerial.h>

SoftwareSerial sSerial(10, 11);  //(RX:D10, TX:D11)
ESP8266 wifi(sSerial);  //建立名為 wifi 的 ESP8266 物件

void setup() {
  Serial.begin(9600);  //啟始硬體序列埠 (debug 用) 
  Serial.println("*** WeeESP8266 WiFi Library Function Test ***");
  Serial.println("Firmware Version ... " + wifi.getVersion()); //傳回 AT 韌體版本 
  Serial.println("Local IP ... " + wifi.getLocalIP()); //傳回本地 IP
  Serial.println("List available APs ... ");
  Serial.println(wifi.getAPList().c_str()); //傳回附近可用的 AP  
  }

void loop() {
  if (sSerial.available()) {   //若軟體序列埠 Rx 收到資料 (來自 ESP8266)
    Serial.write(sSerial.read());  //讀取後寫入硬體序列埠 Tx (PC)
    }
  if (Serial.available()) {  //若硬體序列埠 Rx 收到資料 (來自 PC)
    sSerial.write(Serial.read());  //讀取後寫入軟體序列埠 Tx (ESP8266)
    } 
  }


其輸出為 :

*** WeeESP8266 WiFi Library Function Test ***
Firmware Version ... 0018000902
Local IP ... 192.168.2.106
List available APs ...
+CWLAP:(3,"leeyuyun",-91,"b8:a3:86:94:f8:e8",1)
+CWLAP:(2,"edimax.setup",-69,"74:da:38:15:16:00",1)
+CWLAP:(3,"Home",-76,"c4:e9:84:66:33:c1",2)
+CWLAP:(4,"EDIMAX-tony",-52,"80:1f:02:2d:5a:9e",11)
+CWLAP:(3,"MOTOROLA-5N6F",-84,"f8:35:dd:74:d2:b6",11)
+CWLAP:(4,"alex",-92,"fc:75:16:01:26:0c",6)
+CWLAP:(3,"TP-LINK_601A04",-71,"e8:de:27:60:1a:04",10)

可見 getAPList() 函數是沒問題的, 我懷疑在上面測試 1 程式是不是耗用比較多記憶體, 導致 memory leak 呢? 此程式耗掉 25% FLASH, 37% SRAM, 相較於測試 1 的 34% FLASH 與 52% SRAM 確實少很多, 不過看起來還夠呀! 奇怪?

當附近可用 AP 少時, 上面這個程式執行 OK, 但若 AP 一多時就會秀逗無回應, 怎樣才是多是個臨界值問題, 需要時間去測試與檢查記憶體使用情形. 其實, 這個函數在 Arduino 的應用中並不是很重要, 我們通常固定連線某個 AP, 知道附近有哪些 AP 並沒有多大意義.


沒有留言:

張貼留言