Python可以這樣玩(27):ESP8266 and IoT


接下來我們的焦點就要回到 ESP8266上面,要讓 ESP8266連上 Arduino UNO,其實還有很長的路要走,我看了很多網路的分享,有些人上手的測試大概就花了兩個月,中間可能燒壞了好幾個 ESP8266,還好我不是先驅,所以我會整理出最佳方式,讓初學者可以輕鬆上手。

ESP8266是深圳樂鑫資訊(信息)科技公司開發的Wi-Fi晶片(芯片),只有32個接腳(引腳),而深圳安信可科技用這顆晶片開發出12種小型電路板,分別為ESP-01ESP-12。如下圖:




不只安信可科技一家公司用ESP8266晶片做Wi-Fi小卡,也有其他板卡商拿這顆晶片去做小卡,例如北京多奧雲智科技做成DWA8,或位在紐約市的創客知名公司Adafruit也有做。

甚至現在已經出現了 ESP8266Arduino 整合的板子,例如 ESP-12E ESP-13(光華商場就可以買到,連說明書800多塊)都是。但我所使用的,就是目前大家討論最多的 ESP-01(新版是黑色板)

ESP-01雖然便宜,但是卻是個設計上,與 Arduino和麵包板格格不入的板子,沒有說它不好,就是格格不入。這麼說是讓大家先有心理準備,不過它真的很便宜,我們就用吧。

不相容的腳位設計

下圖是 ESP-01的腳位,它的腳位設計蠻奇怪的,無法直接插入麵包板




所以可以看到網路上有人分享,自己做一個轉接板,接插座跟接頭弄成適合麵包板插的形式,如下圖,當然,市面上也可以買到現成的,但是我去光華商場問過了,沒賣,上網找的話可以找到:





因此,最方便的解決之道就是買一包公對母的杜比接頭線,公頭就可以直接插到麵包板或者 Arduino 上,母頭則是接 ESP-01

3.3V電壓與不相容的電流

ESP8266吃的是 3.3V的電壓,還可以高興一下,因為 Arduino UNO可以提供 3.3V的電壓,不幸的是Arduino官方規格寫 3.3V 只能提供 50mA 的電流,而 ESP8266 會吃超過 100mA 的電,理論上要外接電源,但是我覺得歐洲人在寫規格的時候會比較保守,雖說只提供 50mA的電流,但是超過也可以運作,一般來說,開 STA 模式傳資料時,ESP-01最多吃 100mA,沒出什麼問題,所以為了方便玩家入門,不用另外供電。

可是如果是要燒錄,也就是進入韌體更新模式,就會用到 200 – 300 mA的電流,這是後 Arduino 供電就會有問題,板子就不穩定,因此必須使用獨立的 3.3V電源。目前來說,建議不要急著測試燒錄,很多玩家在網路上都建議更新韌體,要忍住,我的原則還是選擇最簡易的方式來連線 IoT

占用 Arduino RX/TX 腳位

真是一波未平一波又起,ESP8266 RX/TX腳位必須接在 Arduino Uno D0/D1腳位 (板子上標記的:RX <- 0TX -> 1),但是這樣接了之後,ESP8266 就佔據了電腦跟 Arduino RX/TX,如下圖:






這樣一來,首先,你會無法上傳韌體,要先拔掉才能上傳,其次,上傳完畢插進去之後,又會佔據 Arduino Serial Port,你就看不到 Serial.print 的結果,這對程式除錯來說,是個大麻煩。

還好這個問題我們可以用軟體來解決,我們先避開 D0/D1、一般可以使用D10/D11或是 D2/D3,如下圖,就把ESP TX接在D2RX接在D3 (這個順序就不重要,必須看後面程式如何定義,我通常會反過來接)





然後,在 Arduino 的程式中,使用SoftwareSerial 程式庫,在程式中重新定義ESP8266 RX/TX D3/D2,不過有一點要注意, SoftwareSerial 支援 Buad rate 不超過 19200,如果韌體預設不是 9600 的就要改 buad rate

這裡有個概念要提,RX/TX是相對應的,ESPRX要接到 Arduino TX,而ESPTX則是要接到Arduino RX。在前面接D0/D1的圖中,可以看到 Arduino 板子上的標記:RX <- 0TX -> 1,這是表示,D0是接其他設備的 RX腳位,所以其實 D0 Arduino TX,同理,D1則是 Arduino RX

要講這個是在後面會談到的程式碼中會出現 SoftwareSerial ESP(2,3) 這樣的寫法,它的語法如下:SoftwareSerial ESP(RX,TX),這表示把 Arduino D2 定義為Arduino RXD3定義為TX,那麼在接法上,D2就必須接 ESP TXD3就必須接 ESPRX。弄清楚這個概念之後,無論後面你接哪個PIN,程式碼都不會弄錯。如果你把 ESPRX接在D2TX接在D3,程式就會寫成:SoftwareSerial ESP(3,2); //RX, TX(註解中的RX, TX指的是Arduino)

ESP RX無法承受5V

最後的大問題來了,就是電壓問題。前面有關於供電問題,我們勉強用 Arduino 上面的 3.3V來克服了,但是接下來的問題是,我們把 ESPTX接上D2ESPRX接上D3,當ESP TX 傳一個 3.3V的高電位給Arduino的時候,不會有問題,因為 Arduino 雖然是 5V,但是它會把 > 2.0V的電位當成 1< 0.8V的電位當成0,不會有錯。但是當 Arduino 5V的高電位給 ESP的時候,由於ESP 3.3V,可能就會把ESP燒掉。

解法有三個,第一就是不管它,很多人在網路上面分享,即使不管它,ESP還是可以正常運作,的確,很多書上也這樣做,但是,使用時間一長,我們很難保證ESP不會被燒壞,所以即使這是最方便的方法,我也不贊成如此做。

第二種解法是利用電位轉換器(Level Shifter)的方式來做,如下圖:





D3RX之前不可以直接連,必須多接一個 1K歐姆電阻,這樣還沒完,還要多接一個 2K歐姆電阻再接地。利用公式就可以算出 5V/(1K+2K) = x/2Kx = 3.3V,這樣就可以保護ESP不被 5V的電壓衝擊。

第三種方法是參考 超圖解物聯網IoT實作入門這本書,我引用該書的插圖如下:



圖中的板子是ESP-12E的整合開發板,但是道理是一樣的。Arduino UNO透過SoftwareSerial程式庫,將數位23腳模擬成UART序列埠。由於Arduino的數位輸出高電位是5V,為了避免損壞ESP8266ArduinoTXESP8266RX之間用一個1N4148開關二極體串連,如下圖,1N4148的原理是,當Arduino 傳低電位0,可以通過,但是如果傳高電位5,則被擋住,這是ESP因為有上拉電阻而產生 3.3V的電位,視同接收到高電位:



經過PK,我決定採用 超圖解物聯網IoT實作入門這本書的作法,至於接法我也比照,但是上面的接線圖用的是 ESP-12E,我必須把它轉換成 Arduino UNO + ESP-01,完成圖如下:



最左邊的橘線接 3.3V到麵包板下方正極,左邊第二條藍線則是將GND接到麵包板負極,D2的綠線接出來到麵包板連接 1N4148 (我連到左邊有黑環那頭)1N4148的右側則是連到 ESPRX。剩下的就是黃線接ESP TX,右邊的紅線橘線接麵包板正極,咖啡線接麵包板負極。


我是故意沒有另外繪製線路圖的,因為這篇文章是心得分享,我希望看文章的人能夠真正懂我所要表達的東西,所以必須自我測試一下,由上面的說明成功的把線路接出來(真的不行請留言)

測試程式碼

連接好了之後,我們就要準備一個簡單的程式碼,來測試儀下ESP8266是否可以正常運作:

#include <SoftwareSerial.h>
SoftwareSerial ESP8266(3,2); // Arduino RX, TX
void setup()
{
  Serial.begin(115200);
  ESP8266.begin(115200);
}
void loop()
{
  if(ESP8266.available())
    Serial.write(ESP8266.read());
  else if(Serial.available())
    ESP8266.write(Serial.read());   
}  

這個程式就是透過Serial 監視器,與ESP8266溝通,首先要注意的是速率,ESP8266 內定的鮑率有兩種,一是 115200、另一是 9600,我假設新的板子都是使用 115200,所以我的程式透過 115200 進行溝通,請參考下圖:


程式上傳應該沒有問題,上傳後請打開 Serial Monitor,要正確執行,必須修改狀態列下面的兩個下拉選單,請按照上圖改成 NL & CR 115200 baud,修改之後,請在上方輸入 “AT”,按下 [傳送]就可以看見有一個OK的回應。這樣就可以確定正常了。

執行的結果也在上圖,有回應,可是有些BUG,就是當下達 “AT+GMR” 指令的時候,傳回的資料不完整。這也許跟 115200的鮑率有關係。所以,我們馬上下指令把鮑率改成 9600,請輸入 “AT+UART_DEF=9600,8,1,0,0”,然後按下 [傳送]。接著您就會看到OK的回應,但是就再也無法通訊了,如果要通訊,請回到程式碼,將裡面的 115200改成 9600,再上傳一次即可,如下圖,現在再輸入 “AT+GMR” 命令的時候,就會回應正確的字串了




加入AP

確定我們的Arduino ESP 通訊沒有問題之後,我們就要進一步,讓我們的設備連上AP。這個概念很簡單,就把它想成我們讓手機連上家裡的AP,然後就可以連到外面的網路,也就是說我們就可以連上 IoT 平台了。

要連接AP之前,我們先將 ESP 設定成 STA 模式,也就是Client的模式,請輸入指令:

AT+CWMODE=1

看到OK之後,再輸入下面的指令列出所有的AP

AT+CWLAP

這時候就會列出所有可以搜尋到的AP,如下圖:





最後請透過CWJAP指令來連結AP
AT+CWJAP="Great-Fortune_as","***********"

成功連線之後會得到下面的訊息:
WIFI CONNECTED
WIFI GOT IP
很好,成功之後,我們就要開始製作溫濕度的IoT監控了。

DHT11 溫溼度上傳IoT

還記得第21課中DHT的程式碼吧,如下:

#include <dht.h>
dht DHT;
#define DHT11_PIN 7

void setup(){
  Serial.begin(9600);
}

void loop()
{
  int chk = DHT.read11(DHT11_PIN);
  Serial.print("Temperature = ");
  Serial.println(DHT.temperature);
  Serial.print("Humidity = ");
  Serial.println(DHT.humidity);
  delay(1000);
}

當然,也要將DHT11安裝在我們的麵包板上面,接5V的電源,並連接到 PIN7 (21課相同),如下圖:




最後,將這段DHT程式碼與ESP程式碼整合在一起,然後再將前一個單元談到的 ThingSpeak GET字串,就可以將溫溼度每五分鐘 Update 一次(ThingSpeak 好像接受每兩分鐘上傳一次,設定五分鐘就好,不要太接近,以免浪費網路資源),完整的程式碼如下:

#include <SoftwareSerial.h>
#include <dht.h>
dht DHT;
#define DHTPIN 7
#define SSID "Great-Fortune_as"
#define PASSWD "**********"      //輸入自己的
#define IP "184.106.153.149"     //thingspeak.com

SoftwareSerial ESP8266(3,2);      //RX, TX
const int WIFIled=13;
boolean FAIL_8266 = false;
String cmd;
int i;
unsigned long realtime=0;

void setup()
{
  pinMode(WIFIled,OUTPUT);
  digitalWrite(WIFIled,LOW);  
  ESP8266.begin(9600);
  Serial.begin(9600);         //把訊息顯示在 Serial
  for(i=0;i<3;i++)
  { 
    digitalWrite(WIFIled,HIGH);
    delay(200);
    digitalWrite(WIFIled,LOW);
    delay(200);
  }
  do
  {
    sendESP8266cmd("AT+RST",2000);
    Serial.println("reset 8266...");   
    if(ESP8266.find("OK"))
    {
      Serial.println("OK");
      if(connectWiFi(10))           //連線AP
      {
        FAIL_8266=false;
        Serial.println("connect success");
      } 
      else
      {
        FAIL_8266=true;
        Serial.println("connect fail");
      } 
    }
    else
    {
      delay(500);
      FAIL_8266=true;
      Serial.println("no response");
    }     
  }while(FAIL_8266); 
  digitalWrite(WIFIled,HIGH);
}
void loop()
{
  if((millis()-realtime)>=300000)    //300 Update一次
  {
    realtime=millis();
    if(!FAIL_8266)
    { 
      int chk = DHT.read11(DHTPIN);
      updateDHT(String(DHT.temperature),String(DHT.humidity));
    }     
  }
}
void updateDHT(String T,String H)
{
  String cmd="AT+CIPSTART=\"TCP\",\"";
  cmd += IP;
  cmd += "\",80";
  sendESP8266cmd(cmd,2000);
  if(ESP8266.find("OK"))
  {
    Serial.println("TCP OK");
    cmd="GET /update?api_key=MSFPC7MCUNU9***N"; //輸入自己的
    cmd+="&field1=" + T + "&field2=" + H + "\r\n";
    ESP8266.print("AT+CIPSEND=");
    ESP8266.println(cmd.length());
    if(ESP8266.find(">"))
    {
      ESP8266.print(cmd);
      Serial.println(cmd);      //把命令顯示出來
      if(ESP8266.find("OK"))
      {
        Serial.println("update OK");
      }
      else
      {
        Serial.println("update error");       
      }
    }
  }
  else
  {
    Serial.println("TCP error");
    sendESP8266cmd("AT+CIPCLOSE",1000);     
  } 
}
boolean connectWiFi(int timeout)
{
  sendESP8266cmd("AT+CWMODE=1",2000);             //Set station MODE 
  Serial.println("WiFi mode:STA");     
  do
  {  
    String cmd="AT+CWJAP=\"";
    cmd+=SSID;
    cmd+="\",\"";
    cmd+=PASSWD;
    cmd+="\"";  
    sendESP8266cmd(cmd,1000);
    Serial.println("join AP...");
    if(ESP8266.find("OK"))
    {
      Serial.println("OK");
      sendESP8266cmd("AT+CIPMUX=0",1000);              
      return true;
    }
  }while((timeout--)>0);
  return false;
}
void sendESP8266cmd(String cmd, int waitTime)
{
  Serial.println(cmd);
  ESP8266.println(cmd);
  delay(waitTime);
}

上傳之後,就開始上傳資料了,進入自己的 ThingSpeak 首頁,結果如下:





上面的圖形可能看起來很奇怪,因為前面幾筆資料是我在前一個單元裡面自行輸入的,最後一筆則是透過 ESP8266 上傳的。任何時候,只要進入 ThingSpeak.com 網站,Sign In 自己的帳號,就可以看到溫度記錄的結果,如下圖:





前面比較密集的點,是我兩分鐘上傳一次,後面我改成了每五分鐘上傳一次,建議每五分鐘上傳一次就好,這樣資料點看得比較清楚。在上傳的過程中,我使用 Serial Monitor 去觀察結果,發現上傳的成功率是二分之一,請看下面紅色的部分:

AT+CIPSTART="TCP","184.106.153.149",80
TCP OK
GET /update?api_key=MSFPC7MCUNU9K2KN&field1=22.00&field2=66.00

update OK
AT+CIPSTART="TCP","184.106.153.149",80
TCP error
AT+CIPCLOSE
AT+CIPSTART="TCP","184.106.153.149",80
TCP OK
GET /update?api_key=MSFPC7MCUNU9K2KN&field1=22.00&field2=65.00

update OK
AT+CIPSTART="TCP","184.106.153.149",80
TCP error
AT+CIPCLOSE

我個人是認為應該是 Server 的問題,很可能是我們用的是免費帳號,一律接受一次拒絕一次,因為實在是太規律了。關於這點,要等到我買了付費的帳號之後才能驗證了。

到這裡,Arduino 先暫時告一段落,接下來我們就會進入 Raspberry Pi + Python 的世界。

留言

這個網誌中的熱門文章

Python可以這樣玩(16):共陰/共陽七段顯示器

Python可以這樣玩(15):蜂鳴器與音樂

Python可以這樣玩(13):外部LED控制