冬營營招生中

2019年7月21日 星期日

[micro:bit Maker] 用ESP-01模組(ESP8266)打造micro:bit網頁伺服器




ESP-01/ESP-01S是市面上最小最便宜的ESP8266 Wifi模組之一,也是許多人學習物聯網(IoT)應用的入門管道。ESP8266本身其實也是個開發板,不過出廠時預載的韌體允許使用者以外部開發板和它溝通──透過所謂的「AT指令」。



筆者購買的ESP-01S,不過較舊的ESP-01也適用




接線方式如下:

  • Pin 8/Pin 4 → 3.3V
  • Pin 1 → GND
  • Pin 2(Tx)→ micro:bit的Rx(在本例是P16)
  • Pin 7(Rx)→ micro:bit的Tx(在本例是P15)



為了示範方便,這張圖的Tx和Rx接在P0和P1。
當然,由於使用Nexus:bit擴充板的關係,我們實際上改用P15/P16。
當然,你也能在下面的程式中修改Tx_pin和Rx_pin常數。






要注意ESP8266需要至少400mA的供電量才能正常運作。有時ESP-01可能在通電後未能正確啟動,這時就得手動重新接電。

此外,為了讀取ESP-01丟出的序列回應,我們也接上一個USB轉TTL模組,好在電腦上監看ESP-01的運作狀況。

下面是完整程式碼。我們之所以使用MakeCode編輯器的JavaScript模式,主要是為了能輸入AT指令結尾要求的CR+LF,也就是\u000D\u000A。此外,這兒需要處理不少字串,用MakeCode的積木做起來不太方便。

程式開頭的常數WIFI_MODE可用來設定採用STA(站點)或AP(熱點)模式,前者會連上你所在環境的Wifi分享器,後者則會讓自己變成Wifi分享器。下面的常數SSID_x和PASSWORD_x是兩個模式要使用的連線名稱與密碼。

注意:若使用STA模式,不要把含有連線名稱及密碼的程式碼隨意分享在網路上。

完整的AT指令與用法可參考這裡



const WIFI_MODE: number = 2 // 1=STA模式; 2=AP模式 const Tx_pin: SerialPin = SerialPin.P15 // 接ESP-01的Rx pin const Rx_pin: SerialPin = SerialPin.P16 // 接ESP-01的Tx pin const LED_pin: DigitalPin = DigitalPin.P2 // LED控制腳位 const SSID_1: string = "your_wifi_ssid" // STA模式要連的wifi名稱 const PASSWORD_1: string = "your_wifi_password" // STA模式要連的wifi密碼 const SSID_2: string = "ESP8266" // AP模式熱點名稱 const PASSWORD_2: string = "microbit" // AP模式熱點密碼 let LED_status: number = 0 let serial_str: string = "" pins.digitalWritePin(LED_pin, 0) serial.redirect(Tx_pin, Rx_pin, 115200) // 設定ESP-01 sendAT("AT+RESTORE", 1000) sendAT("AT+RST", 1000) sendAT("AT+CWMODE=" + WIFI_MODE) if (WIFI_MODE == 1) { // STA模式, 等待連上再繼續 sendAT("AT+CWJAP=\"" + SSID_1 + "\",\"" + PASSWORD_1 + "\"") let result: boolean = wait_for_response("OK") if (!result) control.reset() } else if (WIFI_MODE == 2) { // AP模式 // 開放1個連線頻道, 驗證模式=4 (WPA_WPA2_PSK) sendAT("AT+CWSAP=\"" + SSID_2 + "\",\"" + PASSWORD_2 + "\",1,4", 1000) } // 設定伺服器 sendAT("AT+CIPMUX=1") sendAT("AT+CIPSERVER=1,80") // 顯示IP (在AP模式預設為192.168.4.1) sendAT("AT+CIFSR") // 顯示打勾圖案, 伺服器完成開啟 basic.showIcon(IconNames.Yes) // 處理HTTP請求 while (true) { // 儲存最長200字的序列資料, 以免loss掉 serial_str += serial.readString() if (serial_str.length > 200) { serial_str = serial_str.substr(serial_str.length - 200) } if (serial_str.includes("+IPD") && serial_str.includes("HTTP")) { // 有連線請求 let client_ID: string = serial_str.substr(serial_str.indexOf("IPD") + 4, 1) let GET_pos: number = serial_str.indexOf("GET") let HTTP_pos: number = serial_str.indexOf("HTTP") let GET_command: string = serial_str.substr(GET_pos + 5, (HTTP_pos - 1) - (GET_pos + 5)) let GET_success: boolean = false // 判斷GET指令 switch (GET_command) { case "": // request 192.168.x.x/ GET_success = true break case "LED": // request 192.168.x.x/LED GET_success = true LED_status = 1 - LED_status pins.digitalWritePin(LED_pin, LED_status) break } // 產生HTML並傳給使用者 let HTML_str: string = getHTML(GET_success) sendAT("AT+CIPSEND=" + client_ID + "," + (HTML_str.length + 2)) sendAT(HTML_str, 1000) // 關閉連線 sendAT("AT+CIPCLOSE=" + client_ID) serial_str = "" } } // 寫入AT指令並加上CR+LF function sendAT(command: string, waitTime: number = 100) { serial.writeString(command + "\u000D\u000A") basic.pause(waitTime) } // 產生客戶端HTML function getHTML(normal: boolean): string { let LED_statusString: string = "" let LED_buttonString: string = "" let web_title: string = "ESP8266 (ESP-01) Wifi on BBC micro:bit" let html: string = "" html += "HTTP/1.1 200 OK\r\n" html += "Content-Type: text/html\r\n" html += "Connection: close\r\n\r\n" html += "<!DOCTYPE html>" html += "<html>" html += "<head>" html += "<link rel=\"icon\" href=\"data:,\">" html += "<title>" + web_title + "</title>" html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" // mobile view html += "</head>" html += "<body>" html += "<div style=\"text-align:center\">" html += "<h1>" + web_title + "</h1>" html += "<br>" if (normal) { if (LED_status) { LED_statusString = "ON" LED_buttonString = "TURN IT OFF" } else { LED_statusString = "OFF" LED_buttonString = "TURN IT ON" } html += "<h3>LED STATUS: " + LED_statusString + "</h3>" html += "<br>" html += "<input type=\"button\" onClick=\"window.location.href=\'LED\'\" value=\"" + LED_buttonString + "\">" html += "<br>" } else { html += "<h3>ERROR: REQUEST NOT FOUND</h3>" } html += "<br>" html += "<input type=\"button\" onClick=\"window.location.href=\'/'\" value=\"Home\">" html += "</div>" html += "</body>" html += "</html>" return html } // 等待wifi連線用 function wait_for_response(str: string): boolean { let result: boolean = false let time: number = input.runningTime() while (true) { serial_str += serial.readString() if (serial_str.length > 200) { serial_str = serial_str.substr(serial_str.length - 200) } if (serial_str.includes(str)) { result = true break } if (input.runningTime() - time > 300000) break } return result }






網頁伺服器開啟後,使用者只要用手機連上名為ESP8266的熱點,然後在瀏覽器打開192.168.4.1就行了。micro:bit收到請求後,會把一個動態產生的HTML傳給使用者,顯示在手機上。

在這個簡單範例中,我們可以透過Wifi來遙控接在micro:bit上的一個LED燈。

若使用者想要增加其他功能或按鈕,只要修改判斷GET指令以及產生HTML的程式碼即可。



沒有留言:

張貼留言