2020年8月25日 星期二

ESP32 作為 Web Server

ESP32本身具有WiFi連線的功能,而 ESP32 當作Web Server就是可以將監測的信號傳送到網路上的用戶端或是雲端伺服器。
要將ESP32設定成 Web Server,要有幾個步驟:

一、載入WiFi的函數庫
     
     #include <WiFi.h>

二、設定要連上無線基地台的ID及密碼

    const char* ssid     = "無線基地台的名字";                     
    const char* password = "連上無線基地台的密碼";
    
    ESP32 Web Server成功連上無線基地台後,會得到基地台給虛擬的 IP。因此這個時候在同網段的電腦或是手機,就可以用網頁瀏覽器連上這個 IP,與 ESP32 Web Server連線傳輸。     

三、宣告一個伺服器物件,名稱可以自訂,一般為了容易識別都設成 server;通訊埠也是設定網頁的80。若是要使用其他埠號,則用瀏覽器連上Web Server時,就要加上埠號。
 
    WiFiServer server(80);

     (似乎可以宣告兩個以上的伺服器物件,使用不同的埠號,這以後來測試看看)

四、啟動無線物件開始連結基地台,並不斷偵測無線是否已經連上了。如果沒有連上,每隔0.5秒不斷測試,並在監視視窗顯示狀態。

    WiFi.begin(ssid,password);
    while (WiFi.status() != WL_CONNECTED) {          //利用迴圈偵測無線物件連線的狀態
       delay(500);
       Serial.print(".");
    }

五、啟動 Web Server
    
    server.begin();

六、顯示連上基地台後,分配給 ESP32 Web Server的IP位址。可供同網段的電腦或手機用瀏覽器連線。
    1. 顯示在監視視窗上
        Serial.println("");
        Serial.println("WiFi connected.");
        Serial.println("IP address: ");
        Serial.println(WiFi.localIP());
   
      顯示到連線到無線基地台所分配到的 IP是 192.168.1.116 


    2. 之前使用過 OLED Display,正好也可以將 IP顯示在OLED。這比用USB連線上到ESP32電路板上,才能知道IP位址。
        display.setFont(ArialMT_Plain_16);
        display.drawString(00,00,"WiFi connected.");
        display.drawString(00,20,"IP address: ");
        display.drawString(00,40,WiFi.localIP());
        display.display(); 

    
     在編譯後,OLED顯示器的 drawString()內的參數第3個是字串,跟 localIP()是 IPAddress是不匹配的,所以要把 localIP() 改成 String 資料型態。因此使用String()轉換型態。
     
     String IPAddressofServer = String(Wifi.localIP());
     display.drawString(00,40,IPAddressofServer); 
     

   結果在 OLED 上顯示的 IP 卻不是 192.168.1.116。所以 IPAddress()是整數矩陣型態,無法使用 String()型態轉換。在 Google搜尋了 "IPAddress() to string"的方法,找到了:
      (1).建立一個轉換函數:
           String IpAddress2String(const IPAddress& ipAddress)
            {
                return String(ipAddress[0]) + String(".") +\
                String(ipAddress[1]) + String(".") +\
                String(ipAddress[2]) + String(".") +\
                String(ipAddress[3])  ; 
             }
        
            String IPAddressofServer = IpAddress2String(Wifi.localIP());
            display.drawString(00,40,IPAddressofServer);
     
      (2)後來查到一個Wifi.localIP()內建的方法:WiFi.localIP().toString()
            
            display.drawString(00,40,WiFi.localIP().toString());



















六、現在若是用手機(要在同一個網域,也就是手機也是連上同一台無線基地台)的瀏覽器上,輸入192.168.1.116,是一片空白。接下要設定當有使用端(client)連線上Web Server(ESP32)時,會適當給予回應。
    先宣告一個client物件,是用來 Server有接受到連線時的使用端資訊(client)
     WiFiClient client = server.available();
     if (client) {     
         當有連線到 Web Server,client會接收到連線的資料。
         所以可以使用 client.print(""); 將要顯示在資料顯示在網頁上
     }

   例如:
    void loop() {
        WiFiClient client = server.available(); 
        if (client) {                         
           client.println("HTTP/1.1 200 OK");      //因此顯示第一個顯示網頁的畫面  
           client.println("Content-type:text/html");
           client.println();
           client.print("<h1>");
           client.print(" Web Server");
           client.println("</h1>");     
       }
   }


七、若要控制ESP32(例如傳給值給ESP32的Web Server,將偵測到的溫溼度顯示在OLED上)或是與Web Server互動(再將溫溼度顯示到手機上的網頁),這些首先都是要能把值傳到 ESP32上的Web Server,ESP32再根據接收的資料(控制字元),來判斷要回應甚麼動作。
       
      1.client.read() 是用來讀取網頁上傳送過來的資料
      2.client.connect() 用來確認client端是否還連線在 Web Server上,連線則傳回1,否則傳回0。
                                   我們必須先用這個函數來確認,client還連線在Server,以確保下面讀取到
                                   的資料是由client傳過來的。在測試的時候,曾拿掉這個判斷,會讀到還沒
                                   連線前的資料,造成誤判。
      3.client.available() 用來確認網頁是否有資料可以讀取,如果沒有資料就會傳回0。
      
      4.client傳遞資料的儲存

      String newLine="" ;             //宣告一個字串變數用來儲存clinet傳送過來的資料
      if (c=='\n') {                       //如果讀到的字元是 '\n',表示這個網頁是第一次連上 Web Server。
           if(newLine.length()==0){    //讀到字元\n,而且資料字串是空白,表示clint沒有傳遞資料,
                                                          所以是第一次連到 Server,所以顯示初始的網頁。
             client.println("HTTP/1.1 200 OK");      //初始的網頁,會有一個可以顯示溫溼度的連結  
             client.println("Content-Type:text/html");
             client.println();
             client.println("<!DOCTYPE HTML>");
             client.println("<html>");
             client.print("<h1>");
             client.print(" Web Server");
             client.println("</h1> <h3>");
             client.print("Click <a href=\"/T\">Here</a> to Dispaly Temperature and Humidity.");
             client.println("</h1></h3>");
             client.println("</html>");
             break;                                     //顯示首頁完,跳出while迴圈
           }
           else {          //讀到\n,而資料字串長度不是0,表示已經到client傳遞資料的尾端,清除資料
                                 字串
             newLine="";
           }  
        }
       else if (c!='\r') {  //新的client連線,但讀到資料不是 /n 跟 /r,把讀到的字元存入資料字串     
         newLine +=c;      
       }

    5.判斷資料字串: endsWith用來判斷資料字串(newLine)是否是 "GET /T"結尾的。
             
       if (newLine.endsWith("GET /T")){
           ht();                                                 //呼叫ht()函數,在 OLED上顯示溫濕度  
       }

    6.相關的程式:
   
    void ht(){
         h=dht.readHumidity();
         hh=h;
         t=dht.readTemperature();
         tt=t;
         if (isnan(h)||isnan(t)){
             display.drawString(00,5,"Failed to read from DHT sensor!");
             display.display();
             return;                      
         }
         display.clear();
         display.drawString(00,1,"Humi:");
         display.drawString(60,1,hh);
         display.drawString(90,1,"%");
         display.drawString(00,30,"Temp:");
         display.drawString(60,30,tt);
         display.drawString(90,30,"c");
         display.display();
     }

    void loop() {
        WiFiClient client = server.available(); 
         if (client){                                    //有網頁連線上到 Web Server
             display.clear();
             display.drawString(00,00,"client connected.");
             display.display();
             String newLine="";
             while(client.connected()){
             if (client.available()){                //available()是表示client有傳遞資料 
                 char c=client.read();
                 Serial.write(c);
                 if (c=='\n') {                   //如果讀到的字元是 '\n',表示這個網頁是第一次連上 Server
                    if (newLine.length()==0){
                       client.println("HTTP/1.1 200 OK");      //因此顯示首頁  
                       client.println("Content-Type:text/html");
                       client.println();
                       client.println("<!DOCTYPE HTML>");
                       client.println("<html>");
                       client.print("<h1>");
                       client.print(" Web Server");
                       client.println("</h1> <h3>");
                       client.print("Click <a href=\"/T\">Here</a> to Dispaly Temperature and Humidity.");
                       client.println("</h1></h3>");
                       client.println("</html>");
                       break;                                //顯示首頁完,跳出while迴圈
                   }
                   else {
                          newLine="";
                   }  
                }
                else if (c!='\r') {
                          newLine +=c;      
                }
       
               if (newLine.endsWith("GET /T")){    //呼叫ht()函數,在 OLED上顯示溫濕度  
                    ht();  
               }
            } //available
          } //while
        }
      } //loop


參考資料:
  1.ESP32程式設計(基礎篇),曹永忠,2020年2月,渥瑪數位。

MicroPython在 ESP8266/ESP32(ㄧ) 使用 uPyCraft 開發工具

        除了使用C語言來撰寫控制微控器(MicroController)的程式,目前非常多人使用的Python程式語言也可以撰寫控制程式;只是一般微控器上的記憶體容量並不大,所以無法使用Python的所有功能。因此劍橋大學的 Damien P. George教授開發了 Python的精簡版,能使用在微控器上的 MicroPython。但必須要注意它並非能使用所有Python的函數庫及功能。(註1、2)

         ESP8266及ESP32模組的相片如下,左邊的是ESP8266,右邊的是ESP32。在 Arduino中要燒錄程式到微控器時,都要按右下角的按鈕(BOOT/FLASH)。

        市面上有可以一鍵燒錄的ESP32模組,如下圖。還提供可以外加電源輸入的插槽(左下角)。


        要在 ESP32 或是 ESP8266上使用 MicroPython 前,必須將相對應 MicroPython 的韌體(Firmware)燒錄到微控器上。在 Google上搜尋 MicroPython 就可以找到連接的網址:https://micropython.org/,上方的 DOWNLOAD 網頁內,就可以選擇要搭配相關微控器的MicroPython。

       ESP32:https://micropython.org/download/esp32/

        

        ESP8266:https://micropython.org/download/esp8266/

        網站是使用 esptool.py 來燒錄韌體。但如果對於文字模式的操作有困難,建議可以使用uPyCraft 這套由大陸 DFRoot 公司所推出免費的整合式開發軟體(IDE),它整合了終端機、Python 程式編輯器及軟體線上燒錄的功能。下載網址:https://github.com/DFRobot/uPyCraft。下載zip檔後,解壓縮後,以系統管理員身分執行 uPyCraft 應用程式。

        若是在執行後,出現找不到 MSVCR100.dll 的訊息,請到微軟的下載中心 Download Center
網站:https://www.microsoft.com/en-us/download/details.aspx?id=26999。

        

       Microsoft Visual C++ 2010 Service Pack 1 Redistributable Package MFC Security Update        選擇 Chinese(Traditional) 語系後,按下 Download 按鈕。


        在下載頁面,記得要下載的是 vcredist_86.exe,然後進行安裝。就可以解決問題。
如果選成 vcredist_x64.exe 是沒用有的。猜想  uPyCraft 是不是32位元版的原因。 

        開啟程式後,在 Tools 功能表下,從 board 中選取 esp32,再設定 Serial 中設定 esp32 模組連接的串列埠號(要按各自 esp32 連結到電腦的埠號)。


        若 ESP32模組未安裝 MicroPython韌體的話,uPyCraft會出現下面的視窗:


        此時,可以直接燒錄 uPyCraft所提供的 MicroPython韌體,但還是建議使用 MicroPython官網所提供韌體。burn_addr(燒錄韌體的起始位址)要設定成 0x1000,就可以開始燒錄。

        按下 ok按鈕後,先進行清除(Erase)的工作,如果無法開始清除,試著按住ESP32模組上右下角的BOOT按鍵,直到開始清除。


        清除後,接著就會進行燒錄(Burn)的工作。

        燒錄完,不會出現任何的提示訊息,此時可以按下uPyCraft視窗右邊功能鈕的連結(Connect)。


        連接上 ESP32 模組,在 uPyCraft下半部的 Shell 視窗就會出現 MicroPython 的提示 >>>,可以輸入指令進行測試,另外左邊的視窗,點選 device,會顯示 ESP32模組上的檔案。


        接下來自行輸入一個 python 程式,或是由 範例 (Examples)中選取一個程式,進行測試是否可以在 ESP32 模組上使用 Python,最簡單的測試範例就是可以讓 onBoard 的 LED閃爍。所以選擇 blink.py。


        撰寫好 MicroPython 程式,可以使用功能表 Edit下的 syntaxCheck 進行語法的檢查。
   

        如果有錯誤的地方,例如 while Ture 後面,忘了加上:經過 yntaxCheck 檢查,會在 Shell 視窗出現錯誤的訊息。如下圖:


       語法檢查後,沒有問題就可以在 Tools 功能表下的 DownloadAndRun 命令或是直接按下快捷鍵 F5,將 MicroPython 檔案 (blink.py)上傳到 ESP32 後,執行程式,檢查結果是否正確。


        在 uPyCraft 左邊的視窗,在 device 資料夾中,可以看到剛上傳到 ESP32的檔案 (blink.py), 還有 boot.py。


        如果要執行在 ESP32 內的某個 MicroPython 檔案,可以利用 uPyCraft 左邊的視窗,從 device 資料夾中點選檔案後,按下滑鼠右鍵,選擇 Run 的命令。如果選擇 Default Run,則是 ESP32一接上電源後就會馬上執行程式 blink.py。

 
        選擇 Default Run命令後,會產生一個 main.py檔案,裡面的指令就是 ESP32 接上電源後就會執行的檔案(blink.py)。再按下 ESP32模組上的 RST 或是 EN 按鈕就會看到執行的結果。 如果要停止執行,選擇 Tools功能表下的 Stop命令。


        要注意的是: 如果直接關掉 uPyCraft 程式或是拔起 ESP32模組,再開啟  uPyCraft 或是插回 ESP32模組,此時  uPyCraft 將無法連線上 ESP32模組,會出現重新燒錄韌體的視窗。

       MicroPython檔案系統,在根目錄(device)下常會有兩個 python 檔案:boot.py 與 main.py。在 ESP32開機後,會先去根目錄下找尋 boot.py 檔案,存在的話就先執行此開機檔; 然後再尋找 main.py,如果有的話就會再執行這個程式。 main.py 裡面主要就是一個執行特定應用邏輯無窮迴圈。(註3)

        一旦將 blink.py設定成  Default Run,會產生 main.py 無限迴圈執行檔案,以致關掉uPyCraft程式或是拔起 ESP32模組後 ,uPyCraft 無法連線上 ESP32模組。

       要解決這個問題,若是在設定成  Default Run 後,先從 Tools 功能表下的 Stop 命令停止程式執行,再刪除 main.py,將會出現 delete error。

       

        目前想到解決的方法是:

       1.開啟 main.py

       2.將 main.py內的 exec(open('./main.py').read(),globals()) 程式刪除掉。

       3.再從 Tools 功能表中,選擇 Download命令,將 main.py清除空白。


        如果未清除 main.py 內的 exec(open('./main.py').read(),globals()) ,就關掉 uPyCraft 程式或是拔起 ESP32模組,那除了重新燒錄韌體外,可以選擇用 Thonny 整合開發軟體來刪除 main.py。

        uPyCraft更多的使用方法,請參考註2的說明文件。

        另外要注意的是:使用 uPyCraft 燒錄 ESP8266 韌體時,burn_addr 要設定成 0x0 (預設值)。


註1:超圖解Python物聯網實作入門,趙英傑,旗標出版社(2018)。

註2:uPyCraft簡體中文說明文件:http://docs.dfrobot.com.cn/upycraft

註3:MicroPython on ESP8266 (六) : 檔案系統測試,小狐狸事務所,http://yhhuang1966.blogspot.com/2017/05/micropython-on-esp8266_18.html。

註4:經典ESP學習網站(Random Nerd Tutorials): https://randomnerdtutorials.com/

2019年4月30日 星期二

ESP32+Oled 1306

OLED是相當實用的顯示模組,這次用的是 SSD1306 128X64。要使用 OLED 要先下載相關的函數庫,解壓縮後,並放到 arduino/libraries 資料夾下。函數庫的連結如下:

1. Adafruit SSD 1306 OLED Driver Library
2. Adafruit GFX Library

下載後,可以用 SSD1306 函數庫提供的範例 Adafruit SSD1306下的ssd1306_128x64_i2c做測試:

如果使用的OLED是 128x64,要去修改 Arduino下 ibraries 資料夾中 Adafruit_SSD1306-master 裡的 Adafruit_SSD1306.h ,建議用 Notepad開啟。
Adafruit_SSD1306.h裡面要修改的地方有兩個:
1. 把55行定義的位址 #define SSD1306_I2C_ADDRESS   0x3D 改成 0x3C
2. 再到 73、74行,因為用的OLED是 128x64,所以在 #define SSD1306_128_32 前面加上
     //。並刪除 #define SSD1306_128_64 前面的 //

修改後,記得存檔。


使用Adafruit_SSD1306 OLED提供的範例進行測試,選擇 檔案 / 範例 / Adafruit_SSD1306/ssd1306_128x64_i2c

程式中要記得修改的 display.begin(SSD1306_SWITCHCAPVCC, 0x3D);中的位址為0x3C。



接線的部分,參考網路上提供的接法:

「esp32 oled」的圖片搜尋結果(註1)
執行後的結果,如下影片,這是函數庫中提供的範例:


接下來從範例中修改,在OLED上顯示不同文字大小的文字:
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>                              //掛載 OLED 函數庫
#include <Adafruit_SSD1306.h>                       //掛載 OLED 繪圖及圖形函數庫
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);    //宣告一個OLED物件為display
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16
#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
void setup()   {             
  display.begin(SSD1306_SWITCHCAPVCC, 0x3c);  // initialize with the I2C addr 0x3c (for the
                                                                                          128x64)
  // Clear the buffer.
  display.clearDisplay();
  delay(2000);
}
void loop() {
  display.setTextColor(WHITE);                           //設定顯示文字的顏色
  display.setTextSize(1);                                        //設定第1行文字的大小為1
  display.setCursor(0,0);                                        //設定第1行文字在oled上顯示的座標
  display.println("Hello, world!");                         //設定第1行顯示的文字

  display.setTextSize(2);                                        //設定第2行文字的大小為2
  display.setCursor(0,15);                                      //設定第2行文字在oled上顯示的座標
  display.println("First");                                       //設定第2行顯示的文字

  display.setTextSize(3);                                        //設定第3行文字的大小為3
  display.setCursor(0,40);                                      //設定第3行文字在oled上顯示的座標
  display.println("Second");                                  //設定第3行顯示的文字

  display.display();                                                 //顯示資料緩衝區內的資料
  delay(2000);
  display.clearDisplay();                                        //清除資料緩衝區內的資料
}

顯示結果:



使用 Adafruit_SSD1306 OLED函數庫的話,是不用設定 SDA及SCL接腳,預設就是使用ESP32上的GPIO21(SDA)及GPIO22(SCL);但是換句話說,也就是不能自行設定在其他接腳,這在需要使用多個 I2C 模組的電路,應該會很不方便。在 Google 查詢許多方法,終於找到是修改。

在 C:\Program Files (x86)\Arduino\libraries\Adafruit_SSD1306-master 的路徑下,開啟Adafruit_SSD1306.ccp 檔案。

將第206行的指令敘述 Wire.begin();修改為 Wire.begin(SDA,SCL),參數就是 SDA及SCL的接腳,就可以改用 ESP32 的其他接腳來連結 I2C 的模組。

除了使用 Adafruit_SSD1306函數庫外,還可以使用能和 ESP8266 通用的OLED SSD1306函數庫定。下載的網址   esp8266-oled-ssd1306,這個函數庫的好處,是在宣告 Oled的 SSD1306物件時,直接就告知 I2C 的 SDA及SCL接腳。
           SSD1306  display( Oled模組的位址, SDA接腳,SCL接腳 );



註:
   1.電路圖參考網址:ESP32 Arduino OLED SSD1306 Schematic I2C


2019年4月22日 星期一

ESP32初體驗

最近科裡老師上專題製作課程,買了幾顆ESP32,之前曾使用過ESP8266,得知32比起8266功能多出了很多IO接腳,也含有藍芽(BLE)的功能,所以要了一顆來玩。
(註1)

要能使電腦能和 ESP32聯繫上,必須先安裝驅動程式,要到 SILICON LABS 的網站去下載

CP210x USB to UART Bridge VCP Drivers ,解壓縮安裝完後,在裝置管理員裡的連接埠就會多出一項,Silicon Labs CP210x USB to UART Bridge,後面的 COM4是隨著使用者的電腦而有所不同,這個COM4在 Arduino IDE設定上也很重要。


接著要在 Arduino IDE上,安裝ESP32,首先在 Arduino IDE的選擇 檔案 / 偏好設定。(註2)

在視窗的額外的開發版管理員網址上輸入:https://dl.espressif.com/dl/package_esp32_index.json, http://arduino.esp8266.com/stable/package_esp8266com_index.json,再按下確定的按鈕。

然後在 工具 / 開發板 下的功能表中選擇 開發板管理員。
在開發板管理員上方的過濾尋找欄位內輸入 ESP32,就會找到 ESP32 for Arduino函數庫,按下安裝的按鈕,只要經過幾分鐘就可以安裝好。


安裝好後,就是要進行測試,是否能在Arduino IDE上開發ESP32。
一樣最簡單的測試方式,就是LED的閃爍測試;如同Arduino UNO上有一顆接在第13腳的LED,而 ESP32上也有一顆藍色LED,可以用來做基本的輸出測試。試了第13腳,結果不對,查了網路資料,終於找到是接到GPIO2腳。

#define LED_BUILTIN 2
void setup() {
   pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);                     
  digitalWrite(LED_BUILTIN, LOW); 
  delay(1000);                     
}


在編譯完後,上傳時,在訊息視窗會出現 Connecting......,這個時候要按下扳子上的 BOOT按鈕,等到出現Writing at 0x00010000...(12%),就可以放開 BOOT按鈕。接著就完成上傳程式的部分。


顯示結果:




註:
1. 圖片取自:https://circuits4you.com/wp-content/uploads/2018/12/ESP32-Pinout.jpg。
2. 安裝ESP32到Arduino IDE的相關步驟,請參考:

   Installing the ESP32 Board in Arduino IDE (Windows instructions)