2015年10月30日 星期五

撰寫 Arduino 的 ESP8266 WiFi 函式

ESP8266 的 AT 指令雖然簡單好用, 但若要在 Arduino 程式中發送與處理其回應訊息卻有點繁雜, 因此這兩天便思考如何將每一個 AT 指令寫成函式, 這樣主程式就只要呼叫函式即可, 會簡潔許多. 下面是此次實驗的紀錄, 當然也不是一次就 OK, 而是修修補補幾回才穩定下來.

因為手機以及鄉下與高雄的無線基地台之 SSID 與 PWD 都不同, 每次要用 ESP8266 連線不同的無線基地台做 Arduino 物聯網實驗時, 都必須先使用一條 USB-TTL 轉換線 (我用的是 PL2303HX) 連接 PC 與 ESP8266, 再用通訊程式如 Realterm 或 AccessPort 等連線 ESP8266 對其下達更改連線基地台之 AT 指令, 改好後又要拔掉 PL2303HX 再將 ESP8266 插回轉換板, 這樣杜邦線要拔來拔去實在很麻煩.

上回做 Arduino + ESP8266 連線 WiFi 實驗時, 意外地從 "阿喵就像家" 的簡報中發現 (page 83), 利用軟體序列埠函式庫就可以輕易地將 Arduino Nano 化身為 USB-TTL 轉換線, 可以直接用 Arduino IDE 對 ESP8266 下 AT 指令, 連 Realterm/AccessPort 等通訊程式都免了, 我覺得這方法蠻好用的, 我記錄在之前這篇 :

ESP8266 WiFi 模組與 Arduino 連線測試

下面的 WiFi 函式測試也是使用同樣的方法. 在過去的兩周, 我把 AMS1117V33/LD1117V33 與 1K+2K 電阻都焊在一塊洞洞板上面, 外加一個滑動開關來切換 CH_PD 之可控/不可控, 以及排針排母等元件製作了一個 ESP8266 轉接板, 大大降低了麵包板連線的麻煩, 只要四條線即可將 5V 運作的 Nano 與 3.3V 運作的 ESP8266 相連 , 如下圖所示 :



轉接板參考前篇 :

製作 ESP8266 轉接板

如果不想費工地去焊轉接板的話, 也可以照下面電路圖在麵包板上接線, 只需要 1K+2K 兩個元件而已 :


ESP8266 的 Vcc 取自 Nano 的 3.3v 輸出即可, 但如果發現 ESP8266 不穩定, 通常是電源不夠力, 這時就需要獨立的 3.3V 電源供應, 可用兩個 1.5V 乾電池串聯起來供電. 接好線後, 將下列程式上傳到 Arduino Nano 就可以利用序列埠監視視窗對 ESP01 模組下 AT 指令了, 參考 :

# ESP8266 WiFi 模組 AT command 測試

#include <SoftwareSerial.h>
SoftwareSerial sSerial(10,11); //(RX,TX)

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());  //讀取後寫入硬體序列埠 Tx (PC)
    }
  if (Serial.available()) {  //若硬體序列埠 Rx 收到資料 (來自 PC)
    sSerial.write(Serial.read());  //讀取後寫入軟體序列埠 Tx (ESP8266)
    }
  }

送電後打開序列埠監視視窗, 若出現 "SoftSerial to ESP8266 AT commands test ...", 表示這顆 ESP8266 之 Baud rate 是設定在 9600, 如果出現怪碼或無反應, 那麼 Baud rate 就不對, 可以嘗試將程式中的 9600 都改為 115200 上傳試試看. 我之前跟露天賣家買的一批 20 顆 ESP01 模組 AT 版本為 0.9.5, 預設速率就是 115200 bps, 所以一開始用 9600 連線都出現怪碼, 改成 115200 後即可正常顯示. 連線成功後下指令更改 Baud rate :

AT+UART=9600,8,1,0,0

然後關掉序列埠監視視窗後重開, 更改右下角之 Baud rate 為 9600 即可. 注意, 這個 AT+UART 指令在 0.9.5 版可用, 但在 0.9.2 不行. 韌體版本 0.9.2 的 AT 指令測試參考 :

ESP8266 WiFi 模組 AT command 測試

以下是我針對最穩定的 0.9.2 版韌體, 在上面 SoftwareSerial 函式庫支援下所撰寫的 WiFi 函式, 基本上就是把 AT 指令的操作細節包裝起來而已.

首先是定義一個讀取 ESP8266 回應字元的函式 get_ESP8266_response(), 它會將 ESP8266 傳回給軟體串列埠 RX 的回應字元以字串的 concat() 函式串接成回應字串, 去除頭尾的空白字元 (例如跳行與空格) 後傳回, 這樣我們便能從傳回的回應字串中搜尋關鍵字, 判斷 AT 指令是否執行成功, 或者擷取回應資訊, 例如 IP 等等 :

String get_ESP8266_response() {  //取得 ESP8266 的回應字串
  String str="";  //儲存接收到的回應字串
  char c;  //儲存接收到的回應字元
  while (sSerial.available()) {  //若軟體序列埠接收緩衝器還有資料
    c=sSerial.read();  //必須放入宣告為 char 之變數 (才會轉成字元)
    str.concat(c);  //串接回應字元
    delay(10);  //務必要延遲, 否則太快
    }
  str.trim();  //去除頭尾空白字元
  return str;
  }

注意這裡必須把讀取的字元先放在宣告為 char 的變數中再用 concat 串接, 不可直接丟給 concat, 例如 concat(sSerial.read()), 這樣它會以字元的整數值來串接, 而不是字元. 其次是迴圈裡最後要用 delay() 來延遲一下讀取速度, 否則會太快而讀不到任何東西.

為甚麼要把回應字元抄個副本來處理, 不直接用序列埠的 find() 去搜尋關鍵字呢? 主要的原因是 find() 函式會把接收緩衝區裡讀過的字元讀取過的字元都移除, 導致整個接收緩衝區的完整性被第一次的 find() 函式破壞, 如果需要對回應進行兩次搜尋, 那麼第二次的 find() 可能永遠也不會找到關鍵字, 即使那個關鍵字確實存在, 因為它可能已經被第一次的 find() 抹除了. 參考 :

Does Serial.find Clears Buffer If It Can't Find Anything

在這篇疑難的解答中有提到 "Serial.find() reads Serial buffer and removes every single byte from it", 亦即 find() 每讀取一個字元, 就會將其自接收緩衝區刪除. 但是在官網關於 Serial.find() 的說明卻沒有特別提到這一點, 參考 :

https://www.arduino.cc/en/Serial/Find

其實這主要在 AT+CWMODE 指令會有此問題, 其他 AT 指令大概無此問題. 若工作模式有變, 例如從 1 變 3, 它會回應 OK; 但若不變, 例如原先是 1, 又下 AT+CHMODE=1, 則會回應 "no change". 從下面以 AT 指令操作的回應訊息即可了解 :

AT+CWMODE?

+CWMODE:1     (目前為模式 1=Station)

OK
AT+CWMODE=3    (改為模式 3=AP+Station)


OK      (模式有改變回應 OK)
AT+CWMODE=3     (改為模式 3=AP+Station)

no change    (模式沒變回應 no change)

因此這兩種回應都是表示指令有正常執行, 因此必須做 sSerial.find("OK") || sSerial.find("no change") 的判斷, 但由於 find() 執行後會把緩衝區刪掉, 因此第二個 find() 永遠不會 true. 例如下面這個範例程式  :

#include <SoftwareSerial.h>
SoftwareSerial sSerial(10,11); //(RX,TX) 與 ESP8266 介接的軟體串列埠

void setup() {
  sSerial.begin(9600);  //設定軟體序列埠速率 (to ESP8266)
  Serial.begin(9600);  //設定軟體序列埠速率 (to PC)
  Serial.print("Set working mode as station ... ");
  sSerial.println("AT+CWMODE=1");  //設定為 Station 模式
  delay(1000);
  if (sSerial.find("OK") || sSerial.find("no change")) {
    Serial.println("OK");
    }
  else {
    Serial.println("No response");
    }
  }

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

在執行前先用手動方式用 AT+CWMODE=3 設定為模式 3, 然後再按兩次 Arduino 的 reset 鍵重新執行, 其回應結果為 :

AT+CWMODE=3


OK
Set working mode as station ... OK  (第一次, mode 3 改成 mode 1, 回應 OK)

Set working mode as station ... No response  (第二次, mode 1 改成 mode 1, 回應 No response)

其實往後再按 Reset 重新執行都是 No response, 因為  sSerial.find("OK") 執行完後, 接收緩衝區內已空無一物, 所以 sSerial.find("no change") 當然找不到 no change 啦. 如果使用上面這個 get_ESP8266_response() 函式先讀取接收緩衝區, 將其拷貝到字串變數中就可以避免此問題, 如下列範例所示 :

#include <SoftwareSerial.h>
SoftwareSerial sSerial(10,11); //(RX,TX) 與 ESP8266 介接的軟體串列埠

void setup() {
  sSerial.begin(9600);  //設定軟體序列埠速率 (to ESP8266)
  Serial.begin(9600);  //設定軟體序列埠速率 (to PC)
  Serial.print("Set working mode as station ... ");
  sSerial.println("AT+CWMODE=1");  //設定為 Station 模式
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1 || str.indexOf("no change") != -1) {
    Serial.println("OK");
    }
  else {Serial.println("No response");}
  }

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

  String get_ESP8266_response() {  //取得 ESP8266 的回應字串
  String str="";  //儲存接收到的回應字串
  char c;  //儲存接收到的回應字元
  while (sSerial.available()) {  //若軟體序列埠接收緩衝器還有資料
    c=sSerial.read();  //必須放入宣告為 char 之變數 (才會轉成字元)
    str.concat(c);  //串接回應字元
    delay(10);  //務必要延遲, 否則太快
    }
  str.trim();  //去除頭尾空白字元
  return str;
  }

其回應如下 :

AT+CWMODE?   (手動查詢工作模式)

+CWMODE:1   (目前工作模式為 1)

OK
AT+CWMODE=3   (手動改為工作模式 3)


OK   (按 Reset)
Set working mode as station ... OK   (按 Reset)
Set working mode as station ... OK   (按 Reset)
Set working mode as station ... OK   (按 Reset)
Set working mode as station ... OK   (按 Reset)

可見不管是 ESP8266 第一次回應的 "OK" 或之後的 "no change", Arduino 都會輸出 OK 表示 ESP8266 回應正常, 如此 find() 的問題便解決了.

接下來就可以為 ESP8266 的每一個 AT 指令撰寫函式, 以後直接呼叫即可, 不用再處理繁雜的序列埠讀寫了. 下面是我針對 0.9.2 版 AT 指令韌體所撰寫的 AT WiFi 函式, 以及相關的測試程式, 主要是用在把 ESP8266 當 Station 用的情況, 沒有包含 Station + AP 以及當 Server 時的函式 (以後用到再說) :

#include <SoftwareSerial.h>
SoftwareSerial sSerial(10,11); //(RX,TX) 與 ESP8266 介接的軟體串列埠
String ssid="H30-L02-webbot";  //無線基地台識別
String pwd="blablabla";  //無線基地台密碼

void setup() {
  sSerial.begin(9600);  //設定軟體序列埠速率 (to ESP8266)
  Serial.begin(9600);  //設定軟體序列埠速率 (to PC)
  Serial.println("*** SoftSerial connection to ESP8266 ***");
  Serial.println("Firmware version : " + get_version());
  Serial.println("Baud rate : " + get_baud());
  Serial.println("Get IP : " + get_ip());
  Serial.println("Mode : " + get_mode());
  Serial.println("Set Mode=3 : " + set_mode(3));  
  Serial.println("Mode : " + get_mode());
  Serial.println("Set Mode=1 : " + set_mode(1));  
  Serial.println("Mode : " + get_mode());
  Serial.println("Mux : " + get_mux());
  Serial.println("Set Mux=1 : " + set_mux(1));
  Serial.println("Mux : " + get_mux());
  Serial.println("Set Mux=0 : " + set_mux(0));
  Serial.println("Mux : " + get_mux());  
  Serial.println("Get AP : " + get_ap());      
  Serial.println("Quit AP : " + quit_ap());  
  Serial.println("Get AP : " + get_ap());
  Serial.println("Get IP : " + get_ip());    
  Serial.println("Joint AP : " + joint_ap(ssid, pwd));
  Serial.println("Get AP : " + get_ap());
  Serial.println("Get IP : " + get_ip());
  Serial.println("Connect Google : " + start_tcp("www.google.com",80));
  Serial.println("Send GET : " + send_data("GET /"));
  Serial.println("Connect Thingspeak : " + start_tcp("184.106.153.149",80));
  Serial.println("Send GET : " + send_data("GET /update?api_key=NO5N8C7T2KINFCQE&field1=28.00&field2=82.40&field3=81.00"));        
  }

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

String get_ESP8266_response() {  //取得 ESP8266 的回應字串
  String str="";  //儲存接收到的回應字串
  char c;  //儲存接收到的回應字元
  while (sSerial.available()) {  //若軟體序列埠接收緩衝器還有資料
    c=sSerial.read();  //必須放入宣告為 char 之變數 (才會轉成字元)
    str.concat(c);  //串接回應字元
    delay(10);  //務必要延遲, 否則太快
    }
  str.trim();  //去除頭尾空白字元
  return str;
  }

String get_version() {
  sSerial.println("AT+GMR");  //取得韌體版本
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(0,str.indexOf("\r\n"));}
  }

String get_baud() {
  sSerial.println("AT+CIOBAUD?");  //取得傳送速率
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));}
  }

String get_ip() {
  sSerial.println("AT+CIFSR");  //取得 ESP8266 IP
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(0,str.indexOf("\r\n"));}
  }

String get_mode() {
  sSerial.println("AT+CWMODE?");  //取得工作模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String set_mode(byte mode) {
  sSerial.println("AT+CWMODE=" + String(mode));  //設定工作模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1 || str.indexOf("no change") != -1) {return "OK";}
  else {return "NG";}
  }

String get_mux() {
  sSerial.println("AT+CIPMUX?");  //取得連線模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String set_mux(byte mux) {  //0=single, 1=multiple
  sSerial.println("AT+CIPMUX=" + String(mux));  //設定連線模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String get_ap() {
  sSerial.println("AT+CWJAP?");  //取得連線之AP
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String joint_ap(String ssid, String pwd) {
  sSerial.println("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"");  //連線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(7000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String quit_ap() {
  sSerial.println("AT+CWQAP");  //離線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String start_tcp(String address, byte port) {
  sSerial.println("AT+CIPSTART=\"TCP\",\"" + address + "\"," + String(port));
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("Linked") != -1) {return "OK";}
  else {return "NG";}
  }

String send_data(String s) {
  String s1=s + "\r\n";  //務必加上跳行
  sSerial.println("AT+CIPSEND=" + String(s1.length()));
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf(">") != -1) {  //收到 > 開始傳送資料
    sSerial.println(s1); //傳送資料
    sSerial.flush();  //等待序列埠傳送完畢
    delay(7000);  
    str=get_ESP8266_response();  //取得 ESP8266 回應字串
    if (str.indexOf("+IPD") != -1) {return "OK";}  //傳送成功會自動拆線
    else {  //傳送不成功須自行拆線
      close_ip();  //關閉 IP 連線
      return "NG";
      }  
    }
  else {  //傳送不成功須自行拆線
    close_ip();  //關閉 IP 連線
    return "NG";
    }
  }

String close_ip() {
  sSerial.println("AT+CIPCLOSE");  //關閉 IP 連線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

上面的函式中, 處理上比較特殊的是 set_mode(), start_tcp(), 以及 send_data() 這三個函式, set_mode() 已如上述, 有兩種成功的回應要處理; start_tcp() 是在剖析回應時尋找 "Linked" 這個關鍵字來判斷連線是否有建立; 而 send_data() 則有兩階段剖析, 第一階段是搜尋是否出現 > 符號以便傳送資料, 第二階段是搜尋是否有 +IPD 回應字串, 有的話才是傳送成功. 大部分的操作成功回應 "OK", 失敗回應 "NG"; 有些擷取資訊的例如, get_version(), get_baud() 等則是成功時傳回從回應訊息中過濾取得之資訊.

其次, 在 set_mode(), set_mux() 等函式中, 傳入參數為數值 (byte), 要組成 AT 指令字串時必須先用 String() 將其轉成字串才能串接, 否則會是空白. 另外, 在這個範例中的最後面, 我測試了 TCP 連線函式 start_tcp() 與 send_data(), 分別與 Google 首頁與物聯網伺服器 Thingspeak 建立 TCP 連線, 然後用 HTTP 的 GET 方法向伺服器提出 Request, 從回應可知均能得到正常回應, 檢查 Thingspeak 的 public 網頁, 確實有寫入資料庫. 關於利用 TCP 連線傳送資料, 參考 :

# ESP8266 WiFi 模組與 Arduino 連線測試
# 用 ESP8266 的 TCP/IP 連線抓取網頁

另外, 在上面的函式中, 向 ESP8266 傳送 AT 指令後, 都呼叫了序列埠物件的 flush() 函式, 功能是讓執行程序停住, 直到傳送緩衝區將 AT 指令全部傳送完畢再繼續往下執行, 參考 :

# https://www.arduino.cc/en/Serial/Flush

在下面這篇文章裡有特別提到 flush() 函數功能在 Arduino IDE 1.0 後的改變 :

When do you use the Arduino’s Serial.flush()? 

"The key to that statement is “outgoing”.  Serial.flush() doesn’t clear the “incoming” buffer, like many people think.  It pauses your program until the transmit buffer is finished."

這個函數名稱 flush() 容易讓人誤解為清掉接收緩衝區, 這在 Arduino IDE 1.0 以前確實是如此, 但現在不同了. 想要清空接收緩衝區的話, 必須呼叫 read() 函數一個一個字元去讀取, 每讀取一個字元, 接收緩衝區就會清掉一個字元, 這可以用 while 迴圈來完成 :

while (sSerial.available()) {sSerial.read();} //清空接收緩衝器

最後列出上面測試程式的回應並註解如下 :

*** SoftSerial connection to ESP8266 ***
Firmware version : 0018000902
Baud rate : 9600
Get IP : 192.168.43.151
Mode : 1   (目前為模式 1)
Set Mode=3 : OK  (改為模式 3)
Mode : 3   (目前為模式 3)
Set Mode=1 : OK  (改回模式 1)
Mode : 1   (目前為模式 1)
Mux : 0      (目前為單一連線)
Set Mux=1 : OK    (改為多重連線)
Mux : 1     (目前為多重連線)
Set Mux=0 : OK    (改回單一連線)
Mux : 0     (目前為單一連線)
Get AP : "H30-L02-webbot"    (顯示目前連線之 AP)
Quit AP : OK   (離線)
Get AP : NG    (目前沒有與 AP 連線)
Get IP : 0.0.0.0    (離線時預設 IP 為 0.0.0.0)
Joint AP : OK  (連線指定之 AP)
Get AP : "H30-L02-webbot"   (顯示目前連線 AP 之 SSID)
Get IP : 192.168.43.151   (顯示 ESP8266 取得之 IP)
Connect Google : OK   (連線 Google 首頁成功)
Send GET : OK   (向 Google 首頁傳送 GET 要求成功)
Connect Thingspeak : OK    (連線 Thingspeak 成功)
Send GET : OK   (向 Thingspeak 傳送 GET 要求成功)

可見所有函式均能正常運作. 這次測試時發現, 不知道為何, TCP 連線在 AT+CIPMUX=1 (多重連線) 下無法完成連線, 但只要改回單一連線即可.

其他參考 :

ESP8266 Wifi Temperature Logger

Serial Input Basics
Functions - return array or multiple variables?
Convert serial.read() into a useable string using Arduino?
How to convert int to string on Arduino?

2015-10-31 補充 :

今天在網路上找到跟我上面寫的 WiFi 函數類似的函式庫, 看起來更完整, 是專家寫的吧! 不過它主要是支援 UNO 與 MEGA, 而我寫的是針對 Nano/Pro mini, 參考 :

ITEADLIB_Arduino_WeeESP8266

有時間再仔細看看人家是怎麼寫的, 偷學幾招!

2015-11-01 補充 :

下面這篇的 waitForResponse() 寫法也有參考價值 :

# Using an ESP8266 as a time source (Part 1)


消失的琴鍵

今天在 Youtube 看到李昇基三年前的 MV--重返, 拍得好唯美, 由我最喜歡的 "小煙雨" 金有貞 (金裕珍) 與另一位童星出身的朴健泰搭配演出,  他們倆在 May Queen 裡的精彩演技, 讓我至今印象仍很深刻.

https://www.youtube.com/watch?v=a__1qm-MO1Y


相信很多人都有過青澀的少年之愛, 就算只是暗戀, 也會在內心深處烙下永恆的印記. 因為那通常不會有結果, 因此也是最美. 多年之後暮然回首, 會發現在心裡的某個角落仍然有個真善美存在.

MV 中的金有貞在音樂課裡因為沒能與朴健泰配對彈鋼琴, 只能在後面吹笛子, 於是偷偷地拔掉右邊的琴鍵 ... 下一次上課時, 看著右邊的空位, 她邊吹笛邊偷笑 ... 演得好自然啊!  後來朴健泰不知何故要轉學 (該不是因為揍了害他揹黑鍋的同學吧! 看校長拍拍他的肩膀又好像不是), 金有貞追出去把琴鍵還給他. 他不是要離開學校了嗎? 琴鍵還有甚麼用呢? 其實她想說的是 : 我在意!

金有貞, 1999 年 9 月 22 日生, 與姐姐同年紀, 目前是高二生, 就讀南韓京畿道高陽藝術高等學校. 她在擁抱太陽的月亮中吸睛的演出, 讓後面演長大後的煙雨的大美女韓佳人遜色不少. 我非常看好她未來的發展.

此 MV 的歌詞翻得不錯, 雖然有些地方不太通順 (好像是逐句翻), 但我覺得比下面的這個要自然些, 也較白話 :

李昇基-重返原點 MV 歌詞


되돌리다  ** 重返

알 수 없는 그 계절의 끝  道不清哪個季末

나는 너를 사랑하고 있던걸까?  我那時是否愛過你呢

어딘가에    在某處

우리 함께했던 그 많은 시간이  我們曾在一起那麼久的時間

손 닿을 듯 어제    일 처럼 되돌려지곤 해  彷彿快要碰觸到的手  就像昨天發生的事一樣  **

순간마다 네가 떠올라  總是不斷想起你

조용히 낮게 울리던    그 목소리  總會浮現起你低沉的  那個聲音

봄을 닮은    햇살 같았던 너의 모습까지  包括像春天一樣 像陽光一樣  那時你的樣子

언제나 넌    나의 매일을 환하게 비췄어  無論何時   你照亮了我的每一天

순간마다 네가 떠올라  總是不斷想起你

조용히 낮게 울리던    그 목소리  總會浮現起你低沉的  那個聲音

봄을 닮은    햇살 같았던 너의 모습까지  包括像春天一樣 像陽光一樣  那時你的樣子

아주 작은 기억들 조차 여전히 선명해  就算是很小的回憶   也依然鮮明

알 수 없는 그 계절의 끝  道不清哪個季末

나는 너를 사랑하고 있던걸까?  我那時是否愛過你呢

어딘가에  在某處

우리 함께했던 그 많은 시간이  我們曾在一起那麼久的時間

손 닿을 듯 어제 일 처럼 되돌려지곤 해  彷彿快要碰觸到的手  就像昨天發生的事一樣

순간마다 네가 떠올라  總是不斷想起你

조용히 낮게 울리던 그 목소리  總會浮現起你低沉的  那個聲音

봄을 닮은 햇살 같았던 너의 모습까지  包括像春天一樣 像陽光一樣  那時你的樣子

언제나 넌 나의 매일을 환하게 비췄어  無論何時   你照亮了我的每一天

순간마다 네가 떠올라  總是不斷想起你

조용히 낮게 울리던 그 목소리  總會浮現起你低沉的  那個聲音

봄을 닮은 햇살 같았던 너의 모습까지  包括像春天一樣 像陽光一樣  那時你的樣子

아주 작은 기억들 조차 여전히 선명해  就算是很小的回憶   也依然鮮明

우린 어디쯤 있을까?  我們在哪裡呢

수 많았던 기억들을 되돌려봐  憶起了好多回憶

우린 행복했던 걸까?  我們有曾幸福過嗎?

알 수 없는 마음들만 제자리에 남아  這道不清的心緒  就留在原地吧

순간마다 네가 떠올라  總是不斷想起你

조용히 낮게 울리던 그 목소리  總會浮現起你低沉的  那個聲音

봄을 닮은 햇살 같았던 너의 모습까지  包括像春天一樣 像陽光一樣  那時你的樣子

아직도 난 너를 잊지 않아  直到現在也無法忘記你

우린 어디쯤 있을까?  我們在哪裡呢

우리는 행복했던 걸까?  我們有曾幸福過嗎

*****

"這道不清的心緒  就留在原地吧", 是啊, 就留在心靈深處的某個角落吧.


2015年10月28日 星期三

USB 5V 電源引出線

最近在做電子實驗時常有一個苦惱, 就是手邊好幾個行動電源, 但沒辦法將其連接到麵包板供電給 Arduino 或 ESP8266 的轉接板. 一般的行動電源輸出口採用 USB Type A 母座, 因此要用 Type A 公接頭接出來, 另一端最常用的是 Micro USB 公接頭 (手機用), 或 Mini-B USB 公接頭 (其他 3C 產品如藍芽喇叭, 行車錄影等, Arduino Nano 也是用此接頭). 如果是直接供電給 Arduino Nano, 那麼要用 Type A 公對 Mini-B 公連接線, 再由 參見 :

# Wiki : 通用序列匯流排

如果要將行動電源 USB Type A 的 5V 電源接到麵包板上, 必須自行加工. 之前跟露天盼盼採購電子零件時, 有順便買了兩個 USB Type A 公接頭, 可以用來改裝 :

USB 公頭 A型 4P 三件式 DIY 接頭 充電器電源改裝必備件 USB 公接頭 黑色膠殼 $5

此接頭的黑色膠殼是可以打開的, 賣家產品網頁上有圖片解說如何改裝與接線, 他用的是一般電線, 我則是使用一公一母杜邦線. 接頭內接點有四個, 如圖示右邊為 +5V, 最左邊為 GND, 中間兩個信號線 D+/D- 用不到 :


然後把杜邦線公接頭放進塑膠殼內, 紅線 (+5V) 在右, 黑線 (GND) 在左, 壓緊固定後用烙鐵焊接, 再將塑膠蓋蓋回, 以透明膠帶纏緊即可 :


焊好後用三用電表測量, 充飽的行動電源輸出 5.04V 電壓 :


實際用在 ESP8266 轉接板, 母接頭剛好可以插在上方 VCC 與 GND 排針上, 而 Arduino 的 +5V 與 GND 也是從麵包板接去用 :


做好母接頭的電源引出線後, 另一個 USB 就拿來做一個公接頭的吧! 這次要用公對公杜邦線, 如下圖所示 :


焊好後上場測試 OK :


這樣不管是公或母接頭都有了, 做電子實驗就方便多了, 不僅可將行動電源的 +5V 引出到麵包板, 也可引出 PC 的 USB 電源. 不過, 使用公接頭要注意, 一定要線插上麵包板後再按下行動電源開關送電, 不然兩個線頭容易不小心碰觸, 導致行動電源內之電池短路就不妙了. 這就是我比較喜歡用母接頭的原因.


買 3.5 吋硬碟快捷線 (USB 轉接盒)

2010 年買的舊電腦因為硬碟系統磁區可能有壞軌, 使得 XP 有時開不起來, 因此在思考如何處理二哥抽中的 ASUS  B75M-A i3 主機板時, 接受同事建議把舊電腦拆掉, 機殼拿來裝新主機板. 拆下來的 WD 500G SATA2 硬碟應該只是 C 碟有壞軌, D, E 應該還好, 所以只能拿來當資料硬碟使用.

上網查詢 3.5 吋的外接盒, 發現大概都要 300 元以上 :

# 小齊的家 全新 3.5吋IDE硬碟 外接盒 隨插即用USB 2.0 免驅動 防壓 防震 鋁合金 $320

只有這個全新庫存品含運只要 150 元 :

# 全新庫存 2.5/3.5吋硬碟外接線 SATA/IDE快捷線 $100

昨天收到後馬上拆開來試, 內有變壓器一個, 2.5/3.5 IDE/SATA 轉接盒一個, 以及一條雙頭的 USB 線.



我的 WD 舊硬碟為 SATA 介面, 只要插入 SATA 側的接頭, 將兩個 USB 接至 PC 即可 (插一個可能不夠力, 無法驅動), 硬碟後面那 8 PIN 看似電源線的接頭不用接就可驅動 :



轉接盒上有一個電源開關, 打到 ON 後 Win8.1 馬上就偵測到並自動安裝驅動程式, 盒內的光碟根本用不到. 如此這般, 150 元就讓存有大量重要資料的舊硬碟活過來了, 不須花大錢.


2015年10月26日 星期一

第 43 周記事 : ESP8266 轉接板的新布局

忽忽已來到 43 周矣, 再過 10 周 2015 年即將結束, 年復一年, 歲月如斯.

本周忙著思考如何處理二哥抽獎抽到的 ASUS B75M-A 主機板與技嘉顯示卡, 原先想在 Y 拍買一組特價 1099 的機殼+電源組合, 最終接受同事建議, 拆下已有五年歷史的 XP 舊主機, 加上他給我的一顆舊 400W 電源, 試試看能否驅動顯示卡, 不行的話再買 Y 拍的. 週五中午休息時去建國路二手店花 10 元買了一條大 4 P 對 6 孔的顯示卡電源轉換線, 下班時又去上震科技買了一顆 $1450B 的 Seagate 1T 硬碟, 回家裝上去後送電竟然可以用, Bingo! 最後裝上新硬碟, 安裝 Win8.1 系統以及開發工具等, 做完系統備份即大功告成. 此次升級僅花費硬碟 1480 + 連接線 10=1490, 算是最省的一次, 去年那台老 XP 還花了 2700 哩.

週五晚上終於收到訂購快三個月的菁菁的手機, 試了一下馬上就鄒眉頭, 決定退貨退款. 唉, 挺後悔的, 這暫且不表, 牢騷等退貨處理完以後再說好了. 

週六 (10/24) 晚上參加小學同學會, 應到百餘人 (53 屆共四個班), 實到 12 人. 坐四望五之年有人熱心發動, 這樣的成果要偷笑了. 這可能得感謝 Line 通訊大行其道之賜, 讓失聯同學陸續上線. 雖然我對 Line, 臉書, Google+ 等意興闌珊, 但也不得不肯定一番. 這些工具不是不好, 只要別捨本逐末, 形為物役才是.

週六與周日傍晚都下起大雨, 這是轉冬時常見的現象, 所幸只有兩天, 否則剛發芽的紅豆就糟糕了. 也因為下雨, 原本想去買 10 棵橙蜜番茄來種也沒去, 等下周囉. 現在種的話, 就可以趕上過年了.

本周三把用剩的洞洞板廢物利用, 又再焊了兩個 ESP8266 轉接板, 所以目前一共有六塊了, 排排站照張相吧, 往後六兄弟可要分散各地工作ㄌ :


按照製作的時間先後為由左到右, 其中第 3, 6 號板使用 1/8W 較小顆的 1K+2K 電阻, 其他為藍色 1/4W 的, 我覺得 1/8W 就夠了, 而且體積小較好焊. 最右邊的第 5, 6 號板之 Layout 與前四個板子有些不同, 我在 ESP8266 排母旁邊多加了一個 2*1 排針, 用來在燒錄韌體時插上跳線帽將 GPIO0 腳接地, 其布局圖如下 :

ESP8266 轉換板 v2 背面布局

電路圖如下 :


所以現在背面有兩條跳線了  (紅色) :


從側面圖可看到 GPIO2 的短路跳線針腳 :


轉換板製作到此就暫時告一段落, 六個目前應該夠用了, 預計鄉下太陽能板充放電狀態+夜間照明控制+PIR移動監控使用一塊; 遠端菜園灑水控制器使用一塊; 高雄大門出入+門鈴狀態也要一塊; 剩下三塊實驗用. 以後有需要再加焊即可. 

第二版詳細修改資料我也補充在原先文章的最後面, 參考 :

# 製作 ESP8266 轉接板


ASUS B75M-A 主機板裝機

上週六終於將二哥抽中的華碩 B75M-A 主機板 (含 8G DDR3) 與技嘉 N-750OC-2GI 顯示卡改裝到五年前的舊電腦機殼內, 當然原來的 Power 是推不動的 (雖然也是標 400W), 好在同事從家裡帶來一個核電廠 400W 電源, 雖然是舊的, 但竟然推得動! 在上震買了一顆 Seagate 1T 硬碟, 順利地安裝 Win8.1 後, 效能不錯.

Win8.1 的安裝是先將光碟映像檔的 ISO 燒錄到隨身碟, 再設定 BIOS 為 USB (UEFI) 開機, 這是沒有光碟機的機器最方便的方法, 參考 :

下載Windows 8.1的ISO光碟
安裝Windows 8.1
如何將可開機(BOOT)的ISO映像檔,燒錄到「USB隨身碟」上?
ISO to USB 輕鬆製作 Windows 開機、系統安裝隨身碟
ISO to USB 製作 USB 開機隨身碟、系統安裝隨身碟
UNetbootin – 輕鬆把ISO檔燒錄到USB手指
Windows 8 購買詳細介紹、3大升級 Windows 8 方案指南
即將換掉傳統 BIOS 的 UEFI,你懂了嗎?(一)

我是用 ISO to USB 這個軟體來燒錄, 這軟體有點不方便, 燒錄時不會顯示進度, 你用滑鼠點它會出現 (沒有反應), 但只要隨身碟燈有一直閃, 就表示有在燒錄, 要耐心等.


大約十分鐘左右, 出現下列畫面時就成功了 :


Win8.1 灌好後, 就安裝開發軟體與工具程式, 安裝程序參考上次 Carbonbook 的 :

# Carbonbook 系統更新與軟體安裝紀錄

但這回多加了下列這幾項 :
  1. Bullzip PDF Printer
  2. R 軟體
  3. Picpick 4.0.7
我本來是想買 Y 拍這組機殼電源組的 :

# 杰強 J-POWER 暗殺之星 電競戰神系列機殼+400W電源供應器 $1099

也考慮買 Seagate 這個混合碟, 但我親自到上震去問時, 店員說那是過渡型產品, 不建議買 :

PCHome : Seagate 1TB 3.5吋SATAIII 固態混合硬碟 (ST1000DX001) $2190
SEAGATE 希捷 3.5吋 固態混合 SSHD 1TB ST1000DX001 NAND 8G 7200轉 $1500
# 【上震科技】seagate ST1000DX001 混合碟1TB 聯強 三年保 SSHD 64M 內建8GB 3H000 $2320

同事也表示同樣價格不如買 256G SSD, 速度快效能好 :


我想才 256G 實在太小了, 裝完系統大概只剩 200G 可用. 而上震的店員建議可買 120G SSD 當系統碟, 另外買 Seagate 1T HDD, 加起來不到 3000 元 :

限時!新品降價↘殺ADATA威剛 Premier SP550 120GB SSD 2.5吋固態硬碟 $1299
# ADATA 威剛Premier SP600 128GB 2.5吋 SATA3 固態硬碟 $1499

想想有道理, 混和碟多 8G SSD 就多了 800 元, 實在不划算, 再加 500 元就可以買 120G SSD 了. 但我最後還是只花了 $1480 (含 $30 SATA3 線) 買了 1T HDD 回去試試看效能. 反正有 8G DRAM, 應該不需要 SSD 那麼快的.

# 【上震科技】Seagate 希捷 1TB SATAIII 7200轉 64MB 1TB 3.5吋硬碟 $1450 
ST1000DX001 vs WD1002FAEX 選擇

硬碟可用 HDscan 來掃看看是否有壞軌 :

免費硬碟檢查工具HDDScan 3.3

關於 SSD 參考 :

# Wiki : 快閃記憶體
iPhone 6 / iPhone 6 Plus 使用 TLC 晶片,問題很大嗎?
# ~協明~ Seagate SSHD 1TB 桌上型電腦混合硬碟 - ST1000DX001 / 全新盒裝三年保固 $2190 
# NAND Flash SLC、MLC技術解析
# TLC SSD大軍報到
(固態硬碟)的選購
混血更厲害!第三代Seagate Laptop Thin SSHD混合碟發表!
混合碟近10秒的開機速度?第三代混合碟備份安裝實戰篇

原來 SSD 跟 MOSFET 原理類似, 只是在閘極與通道之間多了一個浮動閘, 寫入時注入的電子會被此浮動閘捕獲而留存, 從而紀錄 0/1 的資訊, 這就是電源拔掉後, Flash 記憶體仍能保存資料的原因, 事實上與 EEPROM 是類似的原理, 只是技術差異使 Flash 速度快多了而已.

另外就是技嘉這塊普通級的顯卡, 還好同事給我的舊電源能驅得動, 但是它沒有顯示卡用的 6P 電源線接頭, 我去順發對面二手店花了 10 元買條舊的, 可以將大 4P 電源接頭轉成顯卡的 6P 使用了. 問過技嘉客服, 顯示卡上有 6P 電源插孔的話一定要接, 否則可能會降速.

# 技嘉 GV-N750OC-2GI 顯示卡規格
顯示卡裝了不能用
(顯示卡) 4PIN轉6PIN / 4P(公)*2轉6P(母) 一分二 電源線 $39
# 《59show》大四P轉SATA電源線【3680701000737】$21


2015年10月25日 星期日

小學同學會

上週因為福龍伯母過世的關係, 久未連絡的同學打電話問我是否能去弔唁, 我說因為要參加公司的鹿港之行無法前往, 但會前一日先去捻香. 同學順便問我今日能否參加小學同學會, 我想他熱情邀約不好拒絕, 當即應允, 但六點半很趕, 因為傍晚回到鄉下要先為爸煮晚餐, 他說晚一點去無妨. 今晚 (10/24) 到達時已過七點. 愛跟的菁菁也去囉! (我的小學同學會她去幹嘛? 記得我這年紀時結婚喜宴等都不想去, 因為大人都會問東問西, 很害羞, 也很煩).

今天只到了十位, 家屬兩位, 還真有點少哩. 畢業這麼多年, 這大概是第二次辦了, 上一次大約是二十年前吧! 有一位已榮升小學校長, 他不僅是小學同學, 也是國中同學, 國中時期常去他家, 兩人曾計畫要製作木造飛機與獨木舟, 順著發電廠排放水道直下我家. 想當然耳, 都沒造成. 沒想到老同學都還記得這些少年狂想.

有一位女同學似乎經商有成, 住在美國, 但常在中美台三地飛行. 她說菁菁很像歐陽娜娜, 很漂亮, 她又沒生小孩, 一直說要我明年帶她去美國作客, 她有很大的宅院 ... 邁阿密與科羅拉多各有一棟 ... 菁菁聽了樂不可支, 我說我比較喜歡舊金山, 還好她沒說 ... 舊金山再買一棟 !

很多人不太喜歡參加同學會, 因為不免會比成就, 而且多年不見有點尷尬, 不知道該說甚麼好. 我也是, 但我不在乎成就甚麼的, 而是我不善應對, 我愛靜, 不喜吵雜. 經過三十多年的歲月淬鍊, 大家或許都世故了, 練達了, 再見面已經不是當年天真的十一歲的心靈. 十一歲時小腦袋瓜到底都在想甚麼?  我全然忘記了, 真想回到那個還不知道甚麼是未來的年代啊!


2015年10月20日 星期二

第 42 周記事 : 鹿港一日遊

今天已週二, 該寫周記了.

今天去蓮潭會館參加工會的勞動教育, 因為中午會有好吃的午餐, 所以雖然巨爵颱風帶來毛毛細雨與灰濛濛的天空, 但一整個早上心情都很 High. 為了避免無聊, 只帶了 iPad 與一本筆記本去, 結果電子雜誌沒看多少, 卻把 ESP8266 轉接板的 PCB 設計圖畫好了, 準備洗板子來節省焊接的時間.

過去這一周主要事件略記如下 :

週二 (10/13) 左營阿姨與小阿姨借我車去花東玩, 週五還車. 週四 (10/15) 水某回台, 雖然聽學妹說克羅埃西亞風景好, 但出差的地點鳥不生蛋又下雨, 不好玩. 週五下班後回鄉下, 直接到同學家為福龍伯母捻香, 當晚又回高雄, 因週六 (10/17) 公司福利會有活動, 鹿港一日遊-回來時已八點, 週日早上再回鄉下, 故本周回鄉下兩趟.

鹿港老街雖然不大, 但保留了許多傳統建築, 透過社區經營再造, 非常值得一遊. 整個下午逛下來也走了三個小時. 當然這得感謝旅行社請當地導遊老師解說, 我們才能有更深的了解, 否則也是走馬看花. 特別是天后宮 (舊祖宮) 的後殿供奉玉皇大帝, 周圍有六十尊神像, 如果沒有老師講解, 我們也不知那原來是每年會輪值的太歲.

桂花巷附近有書法, 彩繪, 手工包包, 皮件, 獅頭製作等文創小屋, 乃日式宿舍改建而成, 甚有特色, 尤其是導遊老師說鹿港人大都寫得一手好書法, 更讓我對鹿港的人文印象深刻.  


 敕建天后宮 (新宮) 乃林爽文事變後所建, 與其他媽祖廟不同的是, 媽祖與千里眼, 順風耳均著官服戴官帽, 匾額書 "敕建天后宮", 以往是專供官方禮拜 :


廟門左方有重建之文武官員下馬碑, 以滿漢文書寫 :


愛跟的菁菁這次因為同事臨時不去頂替其座位, 雖然要走很久, 但是她也說好玩. 不過, 我看是因為在台灣玻璃館, 我買了一個她非常喜歡的手環而樂不可支吧.

週日又買了 20 株玉米種下, 合計已 50 株了. 約莫 12 月時便有新鮮玉米可吃矣.


2015年10月18日 星期日

有臉書之後

在 Yahoo 看到這篇 :

# 用臉書後生活變這樣? 網友看完都哭了

好久以前我也有過這樣的感嘆. 隔壁桌用餐的一對母子, 可愛的兒子手中拿著一朵今天幼稚園裡老師教他們做的康乃馨, 不斷地向媽媽說學校的事, 但那位媽媽卻只顧著滑手機, 口中只是應景地答說 "喔" ... 一對看似情侶的男女坐定後, 除了向服務生點餐外, 沒有說上甚麼話, 各自滑自己的手機, 我看連要吃啥都是透過 Line 溝通的吧!

智慧型手機的問世, 徹底改變了人們溝通的方式. 比電話的發明影響更大. 以前沒有電話的時候, 出外就是失蹤了, 除非遊子寫封信回家, 不然家裡真出了甚麼急事, 只有在報紙頭版下方登報找人了 (還要有看報, 看對報). 電話的普及真的拉近了人們的時空距離.

但是, 即使有了電話, 但人若出去了沒有打電話報平安, 還是失蹤了. 手機的普及, 把人與人的時空距離提升到隨叫隨到的地步. 我覺得這樣其實就夠了, 因為起碼人與人之間還是在 "實體溝通", 而且行動通訊科技就只是溝通的工具而已. 但智慧型手機就不一樣了, 它讓各式各樣軟體走入人們的生活. 以前, 所謂的軟體是那些念電腦的人才會接觸的東西, 但現在有很多人上班若忘記帶手機, 整天大概就毀了.

前陣子我還蠻熱中轉發收到的 Line 給親朋好友, 但有一天我發現收到來自不同好友轉發的同一則訊息, 我就想, 別人收到我轉發的訊息時, 可能也是想 : 這我看過了, 是舊聞了啦! 所以現在我都懶得再轉發任何訊息了. 結果呢, 兩個禮拜下來, 好像大家也沒發現我似乎在 Line 群組中消失一陣了, 我有沒有發訊息根本一點都不重要哩! 其實大家每天都收到太多好友轉的訊息, 少一則根本無關緊要.

最重要的是, 不管是 Line 還是臉書, 這些社交軟體都耗掉我許多時間來看. 按完讚, 回應好可愛喲之後, 在心裡還留下多少印記呢? 關上手機, 我還是得面對自己與孤獨.

"用臉書看著別人的生活", 我到底是在幹嘛呀我.


2015年10月14日 星期三

用 ESP8266 的 TCP/IP 連線抓取網頁

完成 ESP8266 轉接板製作後, 我在想, 既然可以用 GET 方法傳送資料給 Thingspeak 伺服器, 那應該可以擷取遠端網頁的內容吧! 沒錯, 我搜尋 Google 發現這篇 :

# ESP8266 connecting to internet

作者是利用 USB-TTL 轉換線直接與 PC 的 USB 相連, 以 Realterm 下達 AT 指令來讀取 Google 首頁資料. 我則是利用 Nano 的軟體串列埠, 使用 Arduino IDE 下 AT 指令. 參考 :

# 製作 ESP8266 轉接板

#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)
    sSerial.write(Serial.read());  //將 PC 的傳來的字元傳給 ESP8266
    }
  }


此作者也有關於 ESP8266 的介紹 :

# ESP8266 WIFI module
https://nurdspace.nl/ESP8266


首先是與 Google 網頁伺服器建立 TCP 連線 :

AT+CIPSTART="TCP","www.google.com",80

然後告訴 ESP8266 要傳送 7 個 BYTE 資料 :

AT+CIPSEND=7

出現 > 符號後, 傳送 "GET /" 給 ESP8266 :

GET /

傳送的資料有 5 個 BYTE, 加上跳行 \r\n 共 7 個字元. 完整的回應如下 :


AT+CIPSTART="TCP","www.google.com",80


OK
Linked
AT+CIPSEND=7

> GET /

SEND OK

+IPD,496:HTTP/1.0 302 Found
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Location: http://www.google.com.tw/?gfe_rd=cr&ei=dxEeVo_jAYyA0ATLoqSgDQ
Content-Length: 262
Date: Wed, 14 Oct 2015 08:25:27 GMT
Server: GFE/2.0

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.com.tw/?gfe_rd=cr&ei=dxEeVo_jAYyA0ATLoqSgDQ">here</A>.
</BODY></HTML>

OK

OK
Unlink

很奇怪, 得到 302 回應 !


製作 ESP8266 轉接板

完成 ESP8266 與 Arduino 互連測試後, 我就回頭研究製作 ESP-01 模組的轉接板了, 因為總不能每次要用 ESP8266 連網都要準備電阻做位準轉換, 以及一堆公對母杜邦線連來連去的, 看起來很凌亂. 而且經過實驗發現, 一堆杜邦線插在麵包板上, 零件太多, 有時會有接觸不良現象. 以下是此次 try and error 的製作紀錄, 前面的部分雖然有些錯誤, 但我還是保留下來, 後半部才是最後成功的作品.

之前研究過網路上找到的轉接板, 參考 :

# ESP8266 轉接板
Designing a Breadboard Adaptor for the $5 ESP8266 Microcontroller

=== 以下是 try & error 部分 ===

這次要做的轉接板主要是給 5V 運作的 Arduino 與 ESP8266 介接之用. 如果用的是 3.3V 運作的 Arduino, 其 RX/TX 直接與 ESP8266 互連即可, 就不需要這麼麻煩了; 但考量到 ESP-01 模組無法直接插入麵包板做實驗, 轉接板還是需要的, 電路圖如下所示 (使用 Upverter 繪製) :


這裡最主要的是一顆 LD1117V33 的 +3.3V 線性穩壓晶片, 它跟 AMS1117V33 是同樣規格的, 最高輸入電壓 12V, 具有 3.3V 輸出與 800mA 驅動能力, 足以應付 ESP8266 開機時高達 200mA 的啟動電流. LD1117 或 AMS1117 接腳如下圖 :

其次是 CH_PD 與 RX 都採用了 1K+2K 分壓電阻來與 5V 系統的 Arduino 介接. 其中 CH_PD 還用一個 Jumper 接到 3.3V, 如果要節省 ESP8266 功耗, 可以拔掉跳接帽, 讓 Arduino 在不需連網時送 Low 到 CH_PD, 要連網時再送 High, 因此也需要跟 RX 一樣做位準轉換, 參考 :

An IoT Temperature Monitor for my Balcony Garden

我用一塊從禾樺買來的萬用板 (NT$15, 這是我看過品質最好的洞洞板), 先把零件插上去, 如下圖所示 :


左邊有兩排針腳, 各六支, 左邊那排用尖嘴鉗把針腳往下壓到與塑膠座平, 用來插入麵包板中; 第二排與其相連, 用來給母杜邦線連接用. 中間下方是 LD1117V33 穩壓晶片與兩顆 1uF 電容, 上方則是 CH_PD 與 RX 要用的分壓電阻, 最上面是 CH_PD 的跳接帽. 最右邊是給 ESP8266 用的 2*4 排母. 零件佈局接線與接腳安排如下圖所示 :


VCC 輸入, +3.3V 輸出, 以及 GND 針腳安排是配合與 LD1117V33 正面朝左時的腳位相對應, 兩顆緩衝用的 1uF 電容分別跨接 VCC/+3.3V 與 GND 之間. ESP8266 的 RST, GPIO0, 與 GPIO2 都連到 +3.3V 高電位. 而 CH_PD 進來後會經過 1K+2K 分壓電阻接地, 1K 與 2K 串接處連到跳接針腳接 +3.3V, 若不需要控制 CH_PD 時就插上跳接帽保持 High. RX 也是一樣經分壓電阻轉換位準後再接到排母的 RX 腳.

所需的零件如下 :
  1. 1K 電阻 2 個
  2. 2K 電阻 2 個
  3. 2*4 排母 1 個
  4. 1*6 排針 2 個
  5. 1*2 排針 1 個
  6. 跳線帽 1 個
  7. 1uF 電容 2 個
  8. LD1117V33 1 個
  9. 小洞洞板 1 塊


圖中電阻是 1/4W 的, 其實用 1/8W 即可, 比較便宜 (一個 0.25 元). 排母一個約 5 元, LD1117V33 一個約 3 元, 其他電阻電容等合計不會超過 5 元. 洞洞板一塊 15 元可做 4 個, 平均一個 4 元, 整個成本約 17 元.  

接下來進入焊接程序, 首先焊兩排針腳, 因為它們在萬用板上會搖晃, 可能會歪掉不整齊, 所以我先用三秒膠將它們定住 (其實所有零件我都是這樣處理) :  


排針焊好後, 開始焊接穩壓 IC, 這 LD1117 我是直接三隻腳插在圓孔, 剛好到達背面的圓孔銅箔, 我以為這樣焊接就 OK :


LD1117V33 與電容焊好後有量電壓, 正常輸出 3.3V, 但是整個焊接完成後再量, 卻發現 +3.3V電壓輸出僅有 2V 不到, 有時還 0V. 檢查老半天發現穩壓 IC LD1117 這樣插是不對的, 它跟背面的圓孔銅板僅一點點相連而已, 雖然看起來固定住了, 但搖一搖可能使接觸不良, 難怪 +3.3V 輸出變來變去. 其次, CH_PD 的跳接帽設計是錯的, 因為它會跟 LD1117V33 輸出端的電容形成RC 充放電, 使得送電時 CH_PD 要一段時間才會達到 HIGH 準位. 所以第一次焊接結果失敗. 我不死心又再焊一次, 結果當然還是不能用.

=== 以上是 Try & Error ===

為了精簡佈線, 剔除不必要的零件, 將電路修改為如下, 改用 SPDT 開關 (單極雙擲) 取代跳接帽, 當不需要控制 CH_PD 腳時, 切到 +3.3V HIGH 電位; 需要控制 CH_PD 時, 則由 1K+2K 分壓電路將輸入信號 (來自 Arduino) 的 HIGH 位準 +5V 降成 +3.3V. 同時拿掉兩顆防突波電容.


元件實際布局也有修改, LD1117V33 改到背面 (有圓孔銅板那面) 直接與排針連接. 我又再次且戰且走, 從排針開始向排母方向逐一焊接, 沒有預先規劃布局的結果是, 到最後發現背面需要六條跨接線才能完成與 ESP8266 排母針腳的連接, 感覺很凌亂 :

背面 (隨意佈線造成的凌亂)

正面

後來仔細想了一下, 發現如果稍微調整一下佈線方式, 背面就只需要一條跨接線了 :

背面佈線規劃


這裡有三個重點 :
  1. 最上面的 GND 用銅裸線焊在圓孔銅箔上, 一路向右繞過排母到達右下角的排母 GND 腳. 同時, 此 GND 線座標 (1,4) 的圓孔 (在 LD1117V33 IC 的右上角下面), 要用銅裸線穿過圓孔到正面, 到座標 (4,4) 位置時再穿過圓孔回到背面與 RX/CH_PD 的 2K 電阻共接以接地, 如上圖之虛線所示. TX 也是用裸銅線在下方一路向右直達排母左下角的 UXTD 腳.
  2. +3.3V 由 LD1117V33 的頭部銅片向右直達排母的 +3.3V, 同時在 (2,6) 座標位置向下接到 SPDT 的接點給 CH_PD 切換至 +3.3V 用.
  3. RX 與 CH_PD 的 1K+2K 電阻接法較特殊, 為了佈線方便, 1K 電阻從第三行跨到第五行的圓孔, 而 2K 電阻則是跨第四行跨到第五行的圓孔, 共接於第四行, 而且接地到 GND. 
經過這樣的佈線規劃後, 唯一需要用包覆線跨接的, 就只剩下 CH_PD 分壓電阻至排母 CH_PD 腳的這段, 如上圖中的橘色線. 實際焊接結果如下 :

背面 (較整齊的佈線)

注意, ESP01 模組上的 GPIO0, GPIO2, 以及 RST 三支腳沒有用到, 理應接到 +3.3V 為宜, 但我不想再增加跳線, 所以就空接在那邊, 其實也不會有任何影響. 使用三用電表測試排母的 RX 與 CH_PD 電位變化, VCC, GND, 以及 TX 腳的連通性無誤後, 插上 ESP8266 模組如下 :


然後接上 Arduino Nano, 以軟體串列埠介接 ESP8266 轉換板, 用下列程式測試所有 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
    }
  }

然後拔下 ESP8266, 用小電鑽掛上磨片充當電鋸, 將轉換板切下來 :


為了搭配 Fritzing 繪製麵包板接線圖, 我還特地用 Google 網路硬碟中的簡報軟體, 畫了 ESP8266 插上此轉換板的圖案, 左圖是 CH_PD 滑動開關切到 +3.3V, 而右邊則是切到外部針腳由 Arduino 控制 :

 

插到麵包板上與 Arrduino 連線, 這裡我直接從 Nano 的 +5V 針腳對轉換板供電, CH_PD 未進行控制, 故單極雙擲 (SPDT) 滑動開關切在右方 +3.3V 位置, 測試 AT 指令, 可正常執行無誤. 接線圖如下 :



有了轉換板後, 麵包板接線可簡潔多啦! 只要四條線就搞定了! 這樣要做 TCP/IP 相關實驗就不會被一堆線搞得雜亂無章矣. 不過這板子只是單純用來給 Arduino WiFi 連網之用, 無法讓 ESP8266 獨挑大樑作主控器, 因為我沒拉出 GPIO2 腳. 這以後再說吧 (可以考慮取消 +3.3V 輸出, 改為接到 GPIO2 取代, 仍維持 6 支針腳介面). 另外, 此轉換板也不能拿來更新韌體, 因為 GPIO0 腳我也沒有拉出來, 在燒錄韌體時 GPIO0 須接地.

這次一共做了 2 片轉接板, 切剩下的洞洞板還能再做兩個, 別浪費了, 這幾天有空再來焊接, 考慮改用 AMS1117 試試 :


OK, 總算完成了, 這工作花了我整整一個禮拜的大部分閒暇時光, 但是省了以後很多拉線的麻煩, 值得. 我是學電機的, 不是電子的, 以前從來沒焊過電路板, 這第一次的經驗讓我深刻體會到古人說的 "豫則立, 不豫則廢" (中庸第二十章之六). 凡事要先規劃, 才不至於潦潦草草, 雜亂無章. 雖然人算不如天算, 但不算絕對會完蛋.

2015-10-27 補充 :

排母旁邊剛好還有兩個空洞可以放一個 2*1 排針接到 GPIO0, 這樣要燒錄韌體時只要用跳線帽將排針短路即可, 第二版電路圖如下 :


背面焊接點的布局圖修也改為第二版 :


右下角的兩個孔在正面就是插 2*1 排針, 左端點用跳線連接到 ESP01 排母的 GPIO0 腳. 實際焊接圖如下, 可見背面多了一條跳線 (紅色) :


從下列側面圖可以看到此 GPIO0 短路排針, 平常是空著, 要更新韌體時再插上跳線帽即可 :


本想再把 GPIO2 也接出來取代 3.3V 輸出腳位, 這樣將來若燒錄 NodeMCU 韌體後, ESP8266 本身當主控器不需要 Arduino 時, 這個轉換板就變成最簡開發板了. 但還要多一條跳線, 會使背面高度增加不甚平坦. 可行的做法是排母後面再增加一排洞將 GND 線往後移, 讓 GPIO2 用包覆線引出到正面, 再拉到原 3.3V 孔下去背面連接針腳, 而 AMS1117V33 的第二腳剪掉或扳開往上即可.


2015年10月13日 星期二

第 41 周記事

今天已是周二, 雙十假期剛過去, 這是今年最後一次連假了. 因為小狐狸要段考了, 補習班要加強功課, 水某又出差, 我預估周日不一定返鄉下, 故週五早上與姐姐一起回去, 傍晚吃過晚飯再回高雄. 主要是幫爸煮約一周的 "冷凍" 菜.

週二的時候, 水某邀我去漢神巨蛋後面, 統一旗下的健身房免費體驗十天, 我也很好奇健身房到底如何, 就去體驗看看. 原來就是跑步機, 舉重器等等運動, 三樓還有個中型泳池, 設備是不錯, 但要我花錢跑一段路來此運動, 還不如去河堤公園慢跑. 我只去那天就沒去了.

週六我忙著製作 ESP8266 轉接板, 由於第一次使用洞洞板, 加上沒有事先規劃, 結果不成功, 晚上又繼續熬夜做第二塊, 弄到三點才睡, 真是糟糕. 第二天周日起來昏昏沉沉, 本想在高雄休息, 但菁菁說周日不用去補習班, 她已兩周未回鄉下, 想回去看阿公, 所以我也決定回去. 因為阿姨她們本周要跟我借車去台東玩, 還交代車要整理乾淨, 這勢必得在鄉下才能洗車啊! 所以週日下午花了兩個小時刷洗, 反正我這台大灰熊上一次整理是過年前吧! 以前剛買車時每周都擦得亮晶晶哩! 今早載姐姐去凹仔底坐捷運時, 就把機車放在那, 然後走路回家, 到七點半再把車開去那邊把車交給阿姨, 這樣才能趕上班.

寒露過去果然天氣就有點涼了, 節氣轉換還真是明顯. 乍聞福龍伯母仙逝, 本周五等阿姨她們還車再返鄉下弔唁. 人生無常, 如斗之漏砂, 每一分每一秒, 生命不斷流逝, 沒到終點, 都感受不到原來人生是有限的啊!


2015年10月9日 星期五

種鳳梨與蘆筍

因為小狐狸們要補習, 水某又去克羅埃西亞出差, 所以三天連假只有今天能回鄉下, 到傍晚又要回高雄, 當日來回. 姊姊今天沒課, 他說要跟我回鄉下. 先直奔鎮裡的市集, 結果晚了一步, 豆腐賣完了, 但所幸菜攤還剩下兩塊.

其實今天主要是回去幫爸煮冬瓜封與咖哩, 分裝後放冷凍庫. 所以一回到家就開始準備煮菜. 自從小狐狸們陸續上國中要補習後, 我在高雄煮菜的時間等於零, 反而是回鄉下料理的時間多, 也從小舅媽學到幾道菜, 廚藝精進不少.

今天帶同事給我的六株鳳梨苗回去種, 選了舊豬舍後面的籬笆下, 媽以前種絲瓜的地方, 把土堆高, 因為鳳梨不需太多水. 而上周買的 20 株蘆筍也在曬衣架前方, 原先種蔥的那一小畦地種下. 蘆筍莖實在太細, 好像風一吹就會折斷一樣.


過去兩周分別種下 10 株與 20 株玉米, 合計 30 株. 預計下周再種 20 株, 這樣到了冬天就會連續一個月有玉米可以採收了.


下午我在移植雞冠花與圓仔花到前院時, 阿泉伯的兒子雇工前來施肥, 準備要種紅豆. 德華叔去年噴農藥時在田裡摔倒, 加上年齡有了, 決定這一期開始不再佃耕, 阿泉伯的兒子知道後馬上就說換他好了. 種橙蜜番茄的堂弟也聽說了, 跑來問時晚了一步. 現在施肥已經不是像噴藥那樣, 而是有一種施肥機, 長相如下 :


其實就是耕耘機後面加裝施肥的大漏斗而已.


2015年10月7日 星期三

第 40 週記事

過去的一周因為菁菁的補習班展開魔訓, 要到九點半後才回來, 水某又去運動, 整個晚上都只有我在家, 真有點不習慣. 不過也正好專心研究 ESP8266 與 Arduino 的連線問題, 看人家寫的作法似乎很簡單, 但實際做起來卻狀況百出, 經過奮戰, 終於在周日晚上全部搞定.

我會急著完成這項實驗, 主要是因為, 讓嵌入式設備連上 Internet 乃物聯網底層最重要的基礎技術. 雖然短距離無線通訊有 RF, Bluetooth, Zigbee 等等, 但它們都無法直接連上 Internet. 聯網最方便的當然是 WiFi 啦, 誰還要用拉著一條線的 Ethernet 呢! 這就是為何我那塊 WZ5100 乙太網擴充卡買來只用過一次便束之高閣, 改用 ESP8266 了.

周日菁菁要魔訓, 所以只有姊姊跟我回鄉下. 週日 (10/4) 愛心會會員大會, 中午在廟裡辦桌請客. 因為是好日子, 鎮裡有許多人家辦喜事, 有些會員跑不開, 就託當理事的爸代包紅包, 來的人少空桌當然就會多, 所以爸就問我跟姊姊要不要去充人場. 結果早上去市場買的菜中午都沒煮, 直接放冰箱. 吃完飯後又包了一大堆菜回來 (兩份燒酒雞, 一份大豐, 一份肉羹, 一份炒米粉, 都是空桌沒人吃的), 所以週日下午我只照例悶了一鍋冬瓜封, 晚上菜都還吃不完.

週日早上我一個人上市場, 回程又買了 20 株玉米苗, 以及 20 株蘆筍苗回來. 下午在菜園鋤了一塊地, 做高畦把 20 顆玉米種下去, 連同上週買的十株, 總共是 30 株了. 種好已經五點半, 有點晚了, 蘆筍就下周再種好了. 才鋤半個鐘頭就滿身大汗, 比去慢跑流的汗還多哩.


★ Arduino 光敏電阻測試

做完 Arduino 的溫溼度量測數據經 ESP8266 WiFi 模組上傳到物連網伺服器 Thingspeak 後, 我想氣候測量還有甚麼呢? 應該還有三個可以記錄的 :
  1. 亮度
  2. 風向 & 風速
  3. 雨量
這樣就可以形成一個小型測候站了. 這裡面我覺得雨量是比較麻煩的, 風向風速應該不難, 而亮度則是最簡單的, 因為只要用簡單又便宜的光敏電阻 (LDR, Light Dependent Resistor, 因以硫化鎘 CdS 製成, 故通常稱光敏電阻為 CdS) 即可測量. 它是一種可變電阻, 其電阻值依光線之強度而變化, 光度亮時電阻值變低, 暗時電阻值變高. 因此可以利用光敏電阻上壓降的變化來探知光度的強弱.

我參考了下列書籍的範例進行測試 :
  1. 葉難 : Arduino 範例分析與實作設計, 博碩出版, 4-1 節
  2. 楊明豐 : Arduino 最佳入門與應用, 碁峰出版, 8-3-4 節
  3. 孫駿榮等 : Arduino 一試就上手第二版, 碁峰出版, 4-6 節 
  4. 趙英傑, Arduino 互動設計入門超圖解第二版, 6-2 節 
  5. Michael Margolis, Arduino Cookbook, 歐萊禮出版, 6-2 節
亮度量測的原理是使用電阻分壓法, 即將一電阻 R 與光敏電阻 Rcds 串聯, 量取中間的分壓壓降 Vcds (通常是量取光敏電阻上的壓降), 然後將其接至 Arduino 的類比輸入腳量測光敏電阻之壓降 :

Vcds=Vcc*Rcds/(R+Rcds)

串聯電阻的值依光敏電阻大小而定, 若是較大顆的光敏電阻, 其電阻值較低, 則串聯電阻用 1K 即可; 若為一般較小顆的光敏電阻, 則用 4.7K 或 10K 串聯電阻, 電路圖如下所示 :


有些書的接法與上圖中的電阻上下相反 (如 "Arduino Cookbook" 與 "Arduino 範例分析與實作設計"), 即 10K 串聯電阻在下, 因此是量測串聯電阻的壓降. 這樣讀取值就會與亮度成正比, 因光度越亮, 在上的光敏電阻值越小, 壓降也越小, 接地的串聯電阻壓降則越大. 上圖則是越亮讀取值越小.

Arduino 的類比輸入 (Nano 是 A0~A7) 雖名為類比, 事實上輸入信號會被 Arduino 內建的類比至數位轉換電路轉成 0~1023 的數位值, 若 Vcc=5V, 則當輸入也是 5V 時 (全暗, 光敏電阻值最高), 用 analogRead() 讀取時將得到 1023. 由於串聯電阻多少會有一些壓降, 因此要出現 1023 的值必須光敏電阻在全暗環境下, 此時其電阻為 Mega 級, 會吃掉幾乎全部 5V 壓降.

範例 1 :

int value;  //類比輸入讀取值
int LED=13;
void setup() {
  pinMode(LED, OUTPUT);  //設定 DIO Pin 13 為 LED 輸出
  Serial.begin(9600);
  }

void loop() {
  value=analogRead(A0);  //參數也可用 0
  Serial.println(value);
  delay(1000);  //每秒讀取一次
  }

此範例會在序列埠監視視窗顯示所讀取到的光敏電阻壓降值 (0~1023), 每秒讀取一次.  在 "Arduino" 一試就上手這本書裡提到, 由於感測器會受到環境雜訊的干擾, 經過內建 AD 轉換後的 0~1023 數值有時會突然劇烈變動, 通常使用平均值法來處理這些雜訊, 使其對最後之數值輸出影響降低, 例如使用一個 10 次的迴圈連加量測值, 再除以 10 作為輸出值.

比較簡單的方式是採用濾波方式 (積分器), 只取前一次量測值與本次量測值做比例相加後得到輸出值, 如下列範例所示 :

範例 2 :

int value;  //類比輸入讀取值
int LED=13;
void setup() {
  pinMode(LED, OUTPUT);  //設定 DIO Pin 13 為 LED 輸出
  Serial.begin(9600);
  }

void loop() {
  value=analogRead(A0);  //參數也可用 0
  float new_value=0.3*value + 0.7*analogRead(A0);
  Serial.println(floor(new_value));
  delay(1000);  //每秒讀取一次
  }

此處輸出值採前次佔 3 成, 本次佔 7 成方式處理, 這樣數值變化經過積分器濾波後變化就不會那麼劇烈了.

另外, 在 "Arduino Cookbook" 這本書中則介紹了讓光度以 PWM 方式控制 Arduino LED 的閃爍頻率 :

範例 3 :

int value;  //類比輸入讀取值
int LED=13;
void setup() {
  pinMode(LED, OUTPUT);  //設定 DIO Pin 13 為 LED 輸出
  }

void loop() {
  value=analogRead(A0);
  digitalWrite(LED, HIGH);  //用量測值控制亮滅時間, 光度越強, 閃爍越快
  delay(value);
  digitalWrite(LED, LOW);
  delay(value);
  }

以上面的電路圖來說, 此程式光度越亮, 閃爍的頻率就越快. 如下面影片所示 :


光敏電阻最常應用在路燈自動開關上, 當白天太陽升起, 就將路燈關閉; 到了傍晚就開啟. 但是從光敏電阻讀取的數據, 其臨界值到底要用多少作為開啟與關閉的依據? 這要用 Arduino 實際做光度試驗後來決定, 例如大於 700 以上表示天色已暗, 則可用 if 來判斷 :

範例 4 :

int value;  //類比輸入讀取值
int LED=13;
void setup() {
  pinMode(LED, OUTPUT);  //設定 DIO Pin 13 為 LED 輸出
  }

void loop() {
  value=analogRead(A0);
  if  (value > 700) {
    digitalWrite(LED, HIGH);  //路燈開啟
    }
  else {
    digitalWrite(LED, LOW);  //路燈關閉
    }
  }

這裡只是點亮板上的 LED, 只要稍作修改, 改成輸出到控制繼電器的 DIO 腳, 就能真正控制路燈了. 但上面這程式只以一個臨界值做判斷, 當天色變暗剛好在臨界值附近變動時, 路燈就會有閃爍現象, 這可以用兩個臨界值來修正, 如下範例所示 :

範例 5 :

int value;  //類比輸入讀取值
int LED=13;
void setup() {
  pinMode(LED, OUTPUT);  //設定 DIO Pin 13 為 LED 輸出
  }

void loop() {
  value=analogRead(A0);
  if (value > 700) {
    digitalWrite(LED, HIGH);  //路燈開啟
    }
  else if  (value < 600) {
    digitalWrite(LED, LOW);   //路燈關閉
    }
  }

當量測值低於 700 時路燈仍會亮著, 直到低於 600 才會關閉; 若又彈回 600 以上仍是關閉, 直到高於 700 才會開啟. 總之, 雜訊必須每次都大於 100 才會產生閃爍情況, 機率就大大降低了. 設置寬度為 100 當緩衝區就可以消除閃爍情況了.

另外, 因為讀取到的電壓值是 0~1023 的數位化後的數值, 要如何反求光敏電阻上的壓降呢? 可以除以1023 再乘以 5 即得 :

Vcds=5*float(analogRead(A0))/1023

而光敏電阻值也可以從上面的分壓公式反求 :

Rcds=R*Vcds/(Vcc-Vcds)

另外值得一提的是, 在 "Arduino Cookbook" 與 "Arduino 最佳入門與應用" 這兩本書中介紹了一個 map() 函數, 可以把某個值域映射到另一個值域, 例如想將 0~1023 的數值映射到 0~100%, 就可以用 map(0,1023, 0, 100) 來完成.

綜合以上計算式, 我將上面範例 1 修改為下列範例 6 :

範例 6 :

int value;  //類比輸入讀取值
int LED=13;

void setup() {
  pinMode(LED, OUTPUT);  //設定 DIO Pin 13 為 LED 輸出
  Serial.begin(9600);
  }

void loop() {
  value=analogRead(A0);  //參數也可用 0
  float Vcds=5*float(value)/1023;  //光敏電阻壓降
  float Rcds=10000*Vcds/(5-Vcds);  //光敏電阻值
  int percent=map(value,0,1023,0,100);
  Serial.print("value=");
  Serial.print(value);
  Serial.print("(");
  Serial.print(percent);
  Serial.print("%)");
  Serial.print("\t");
  Serial.print("Vcds=");
  Serial.print(Vcds);
  Serial.print("\t");
  Serial.print("Rcds=");
  Serial.println(Rcds);
  delay(1000);  //每秒讀取一次
  }

序列埠輸出擷取如下 :

value=465(45%) Vcds=2.27 Rcds=8333.33
value=465(45%) Vcds=2.27 Rcds=8333.33
value=465(45%) Vcds=2.27 Rcds=8333.33
value=465(45%) Vcds=2.27 Rcds=8333.33
value=204(19%) Vcds=1.00 Rcds=2490.84
value=200(19%) Vcds=0.98 Rcds=2430.13
value=200(19%) Vcds=0.98 Rcds=2430.13
value=199(19%) Vcds=0.97 Rcds=2415.05
value=537(52%) Vcds=2.62 Rcds=11049.38
value=642(62%) Vcds=3.14 Rcds=16850.40
value=636(62%) Vcds=3.11 Rcds=16434.11

這裡百分比越大表示亮度越暗, 100% 就是全暗. 如果要倒過來, 就把 percent 用 100 去減即可 :

int luminance=100-map(value,0,1023,0,100); 

我把這個亮度也加入 Thingspeak 物聯網氣候觀測程式, 名稱從 DHT11 改為 Atmosphere, 標題也改為氣候觀測資料, 參考前篇 :



程式則改為 :

#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() {
  int cds=analogRead(A0);  //讀取光敏電阻壓降數位化數據 0~1023
  int l=100-map(cds,0,1023,0,100); //亮度轉成 %
  float h=dht.readHumidity();         //讀取濕度
  float c=dht.readTemperature();      //讀取攝氏溫度
  float f=dht.readTemperature(true);  //讀取華氏溫度
  //有任何一個是 NAN 就不往下執行資料傳送
  if (isnan(l) || isnan(h) || isnan(c) || isnan(f)) {
    Serial.println("Failed to read from Atmosphere sensors!");
    return;
    }
  blink_led(1000,500); //進入資料傳送程序 : LED 閃爍一次
  //製作參數字串 
  String param="&field1=" + (String)c + "&field2=" + (String)f +
               "&field3=" + (String)h + "&field4=" + (String)l;

  //與 Thingspeak 主機建立 TCP 連線 (用 api.thingspeak.com 亦可)
  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";
  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 字串內容
    Serial.println(GET);  //顯示 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 上收集的數據後, 我發現亮度在 10% 以下就算是昏暗了, 可以點亮路燈; 20% 以上大約是東方魚肚白程度, 可以關閉路燈. 公開的資料觀察連結如下 :

# https://api.thingspeak.com/channels/47985/feed.json?key=PWEAKSDX8X8QP0V5

OK, 大功告成!