本文旨在補充《超圖解物聯網IoT實作入門》第12章「在程式記憶體區儲存靜態網頁」一節(12-31頁),書本的範例把HTML網頁透過C++的Raw String語法存入快閃記憶體。本單元將採用ESP8266的FS.h檔案系統,將網頁和相關資源(如:圖像和JavaScript程式檔)存入快閃記憶體的SPIFFS區域。
ESP8266的快閃記憶體結構
下圖是ESP8266的快閃記憶體的分割概略圖,取自12-24頁的註解以及13-13頁「透過OTA更新ESP8266的韌體」,檔案系統區域的大小取決於ESP8266模組的快閃記憶體大小。
SPIFFS宛如內建於ESP8266的SD記憶卡,可存放程式所需的資料、設定檔或者網站檔案。要存入SPIFFS區域的資料,都得事先擺在程式原始碼的資料夾裡的“data”資料夾(請自行新增“data”資料夾)。例如,本單元程式叫做espStaticWeb,其資料夾結構如下(請按此連結下載本單元的原始檔):
使用ESP8266FS工具上傳資料檔案到SPIFFS區域
負責把資料上傳到SPIFFS的工具程式叫做ESP8266FS,需要另外安裝,步驟如下:
- 下載ESP8266FS工具。
- 將它解壓縮到Arduino IDE安裝路徑底下的tools資料夾:
重新啟動Arduino IDE之後,開啟espStaticWeb.ino檔。你可以執行主功能表「草稿碼→顯示草稿碼資料夾」指令,確認目前的程式原始碼路徑裡面包含data資料夾。
執行主功能表「工具→ESP8266 Sketch Data Upload」(ESP8266草稿碼資料上傳)指令:
IDE將開始上傳data資料夾裡的內容。
當IDE訊息窗格顯示“SPIFFS Image Uploaded”,代表SPIFFS映像檔上傳完畢。
撰寫引用檔案系統的ESP8266網站伺服器程式
本單元的程式改寫自第12章「使用ESP8266WebServer程式庫建立HTTP伺服器」的WiFi_server.ino檔,並加入第13章「設置區域網路域名」的mDNS.ino檔裡面的設定專屬域名和服務回應訊息的程式碼。
凡是有使用檔案系統的程式,都要在程式開頭引用“FS.h”檔。請先在檔案開頭加入引用FS.h的敘述:
#include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> #include <FS.h>
把原本處理網站根路徑請求的rootRouter函式,改成以「唯讀」方式開啟SPIFFFS根路徑裡的index.htm檔,並且交給server物件傳給用戶端。SPIFFS與檔案的相關操作指令說明,請參閱下文「ESP8266檔案系統的相關操作指令」一節。
完整的程式碼如下,setup函式裡面要執行SPIFFS.begin()啟用SPIFFS檔案系統:
#include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> #include <FS.h> const char ssid[] = "你的WiFi網路SSID"; const char pass[] = "你的WiFi密碼"; const char* host = "jarvis"; ESP8266WebServer server(80); void rootRouter() { File file = SPIFFS.open("/index.htm", "r"); server.streamFile(file, "text/html"); file.close(); } void setup( ){ Serial.begin(115200); SPIFFS.begin(); // 啟用SPIFFS檔案系統 WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to "); Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); IPAddress ip = WiFi.localIP(); if (!MDNS.begin(host, ip)) { Serial.println("Error setting up MDNS responder!"); while(1) { delay(1000); } } Serial.println("mDNS responder started"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected! IP address: "); Serial.println(WiFi.localIP()); MDNS.begin(host); server.on("/", rootRouter); server.on("/index.htm", rootRouter); server.onNotFound([](){ server.send(404, "text/plain", "FileNotFound"); }); server.begin(); Serial.println("HTTP server started"); MDNS.setInstanceName("Cubie's ESP8266"); MDNS.addService("http", "tcp", 80); } void loop( ){ server.handleClient(); }
編譯並上傳程式碼到ESP8266之後,開啟瀏覽器連線到jarvis.local或者ESP8266的IP位址,即可看見如下的網頁:
咦…網頁圖片並沒有顯現。這是因為上面的程式只處理來自根路徑(“/”和“/index.htm”)的請求,沒有處理影像路徑“/img/ESP8266.png”的請求(參閱第五章「使用Socket.io建立即時連線」一節的圖說,5-27頁),所以網站無法傳遞影像檔給瀏覽器。
雖然我們可以加入像底下處理影像路徑的請求:
server.on("/img/esp8266.png", imgRouter);
以及對應的處理函式:
void imgRouter() { File file = SPIFFS.open("/img/esp8266.png", "r"); server.streamFile(file, "image/png"); file.close(); }
但很顯然地,這種寫法很不妥當,因為程式無法處理存取非特定資源路徑的請求。相關處理方式請參閱下一篇文章介紹。
ESP8266檔案系統的相關操作指令
底下的指令說明摘譯自ESP8266專案網站的這一篇說明文件。
檔案系統物件(SPIFFS)
SPIFFS.begin()
掛載SPIFFS檔案系統的方法。它必須在其他任何FS API被使用之前呼叫。若檔案系統掛載成功,傳回true,否則傳回false。
SPIFFS.format()
格式化檔案系統。可以在執行begin()之前或之後呼叫。若格式化成功則傳回true。
SPIFFS.open(路徑, 模式)
開啟檔案。路徑必需要斜線開頭的絕對路徑(如:/dir/filename.txt)。模式參數是個用字串指定的存取模式,其值為”r”, “w”, “a”, “r+”, “w+”和”a+”之中的一個,這些模式字串的意義和C語言的fopen函式相同(註:請參閱第七章「利用SD記憶卡紀錄溫濕度變化」一節,7-39頁)。
傳回File(代表「檔案」)物件。若要檢查檔案是否開啟成功,請使用布林運算子:
File f = SPIFFS.open("/f.txt", "w"); // 以「寫入」模式開啟f.txt檔 if (!f) { Serial.println("file open failed"); // 檔案開啟失敗 }
SPIFFS.exists(path)
如果指定的路徑存在,則傳回true,否則傳回false。
SPIFFS.openDir(path)
開啟絕對路徑資料夾,傳回一個Dir物件。
SPIFFS.remove(path)
刪除絕對路徑的檔案,若刪除成功則傳回true。
SPIFFS.rename(原始路徑檔名, 新路徑檔名)
重新命名檔案,路徑必須是絕對路徑,若重新命名成功則傳回true。
Dir(Directory,目錄)物件
Dir物件的作用是遍覽目錄(資料夾)裡的所有檔案,它提供三個方法:next(下一個檔案)、fileName(讀取檔名)和openFile(開啟檔案),它們的用法如底下的範例:
Dir dir = SPIFFS.openDir("/data"); // 開啟“/data”目錄 while (dir.next()) { // 只要還有檔案… Serial.print(dir.fileName()); // 顯示檔名 File f = dir.openFile("r"); // 以「唯讀」模式開啟檔案 Serial.println(f.size()); // 顯示檔案大小 }
在遍覽目錄的過程中,只要還有檔案,dir.next()就傳回true,這個方法必須在fileName()和openFile()函式之前呼叫。openFile()方法接收一個「開啟檔案的模式」參數,這個參數的意義和SPIFFS.open()函式相同。
File(檔案)物件
SPIFFS.open和dir.openFile函式都會傳回File物件。這個物件支援Stream的所有函式,因此你可以使用readBytes, findUntil, parseInt, println以及其他所有Stream方法。底下是File物件特有的函式:
file.seek(位移字元量, 模式)
這個函式的行為就像C語言的fseek()函式。根據的「模式」參數值,它將在目前的檔案中移動:
- 若「模式」值是SeekSet,則從檔案開頭移動指定的字元量。
- 若「模式」值是SeekCur,則從目前的位置位移指定的字元量。
- 若「模式」值是SeekEnd,則從檔案結尾移動指定的字元量。
若位移成功,則傳回true。
file.position()
傳回目前在檔案中的位置,單位是byte。
file.size()
傳回檔案大小,單位是byte。
String name = file.name();
傳回檔案名稱,其格式相當於const char*,若要儲存檔名,請將它轉成String。
file.close()
關閉檔案。執行此函式之後,就不能在該檔案物件上執行其他操作。
趙老師您好:
我在完成13-7頁的動手做時,發現用網頁去操控LED開關和LED照度時,會有延遲,也就是我網頁操作一個動作後,隔了1~2秒,接在esp8266的LED燈才有動作,所以我在找原因出在哪,可能原因如下
1. 在程式記憶體區引用CDN的jQuery和程式碼,是透過網路下載所以變慢
2. ESP8266和網頁之間沒有建立socket,所以沒辦法做到即時
針對1的部分,我才找看看有沒有辦法將jQuery或jQuery mobile的css檔案和js檔案直接燒進esp8266,如果網頁有用到圖檔,也把圖檔燒進esp8266,剛好用老師寫的這篇說明來實做看看
針對2的部分,我在https://swf.com.tw/?p=901有詢問過老師,把ESP8266當作網頁伺服器(server),瀏覽器為用戶端(client),兩者之間建立socket,不知老師是否能建立一個 “建立Arduino的Socket即時通訊程式(三)”來說明這部分
另外在13-30頁下方 的 dtostrf(charTemp, 5, 2, 12.34) 是否應該改成 dtostrf(12.34, 5, 2, charTemp)
另外想請問在13-23頁中文字的圖檔(temperature.png)是用甚麼軟體建立的,我用小畫家 -> 常用 -> 調整大小 -> 選擇像素 ->
水平改成32,垂直改成16 -> 選擇文字把文字放在區塊上,再調整區塊背景顏色(黑),文字前景顏色(白)及透明,發現不太好操作。
hi alex:
ESP8266網站伺服器程式有時候反應比較慢,是因為HTTP訊息比較多的原因,如果是電腦或樹莓派這種主記憶體空間比較大的微控器,就不會出現類似的問題。透過CDN下載或者從本機伺服器讀取的jQuery程式庫,只要下載完成,就會一直待在用戶端裝置的記憶體,所以只有在首次開啟網頁的時候,會比較慢。
我會陸續更新相關內容,本週會先更新一篇「在ESP8266的SPIFFS檔案系統存放網頁檔案(二)」。
13-23頁的中文字點陣圖,我是從Flash輸出的。你可以嘗試免費的Paint.NET繪圖軟體,有中文操作介面。
thanks,
jeffrey
趙老師你好怎麼利用arduino結合ESP8266-01去連上某個網站
不用IP能連上網站?
小丁:
你指的不用IP,是說採用網域名稱(像swf.com.tw)嗎?請使用 AT+CIPSTART 指令連結看看,詳細的指令參數請參閱這一篇留言,安信可公司提供的PDF文件。
thanks,
jeffrey
老師您好請教一下
也就是說ESP系列比AVR單晶片的EEPROM多了一項可讀寫的記憶體空間
使用SPIFFS的方式存取
這樣講對嗎?
嗯,其實就像在硬碟上分割不同的分區一樣。
thanks,
jeffrey
老师您好,我想问一下这句代码server.streamFile(file, “text/html”);中为什么要用streamFile?
streamFile是什么意思?
请参阅上文说明。
thanks,
jeffrey
老師你好~ 請問esp8266有辦法利用arduino 影像模組像是ov7670來傳送圖片到雲端或網站嗎?
可以,ArduCam網站的這篇產品介紹文章裡面就包含了程式庫和Windows與Linux端的程式。
thanks,
jeffrey
老師您好!我來補充一下,Windows 10底下如果在瀏覽器沒辦法開啟http://jarvis.local/的話需要安裝Bonjour。
參考:https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266mDNS
官方下載網站:https://developer.apple.com/bonjour/
需要apple賬號安裝該Bonjour SDK for Windows
感謝補充!(其實書本13-6頁也有講 🙂 )
thanks,
jeffrey
請問此篇的CODE可直接給ARDUINO MEGA2560用嗎?
還是只能給NODEMCU系列的板子
傳上mega都只有編譯錯誤
對,因為微控器的架構不同,只能用於ESP8266系列。
thanks,
jeffrey
老師您好,想問一下若是想要用如您課本中Ch12一樣在程式記憶區儲存靜態網頁(需加入圖片或超連結影片之類的網頁)可以使用Mega2560配合W5100達成嗎?或是說要達成此網頁需用您書中提到的node.js達成呢?謝謝老師!
老師您好,接續剛剛的問題,若是用W5100想要做網頁前端的設計以及想要控制繼電器來開關電器,除了用client.println指令外,還有其他方式嗎?麻煩老師提示了,謝謝老師的指導,多麻煩您了!
client.println指令和控制繼電器兩者並沒有關聯…
如果你打算製作一個圖文並茂、美侖美奐的網頁,並且放在微控器裡面,建議你使用樹莓派之類的高階控制板,而且價格不見得比Mega2560+W5100高。畢竟Arduino板的強項還是在「硬體控制」,能夠存入簡單的網頁並扮演網站伺服器,已經很了不起了。
我不了解你為何要選用Mega2560,就透過網頁控制繼電器來說,ESP8266就夠好用了。
建議你先從基本的硬體輸出∕入控制開始練習,然後再練習網路方面的實作,屆時你自然就會了解它們之間的整合方式。
have fun!
jeffrey
請問老師
開啟12章的範例 WiFi_GET 可以順利執行
但是WiFi_HTML這個範例卻無法成功執行(連線成功後過一段時間序列埠冒出一堆亂碼如下)
不知道是發生了什麼事,是這個範例問題嗎?
Exception (3):
epc1=0x40208b3c epc2=0x00000000 epc3=0x00000000 excvaddr=0x40239c86 depc=0x00000000
ctx: cont
sp: 3ffffc90 end: 3fffffd0 offset: 01a0
>>>stack>>>
3ffffe30: 3ffe8b0b 4020627f 0000001c 401004e8
3ffffe40: 00000000 40204ba9 3ffe8b0b 402049a8
3ffffe50: 00000010 3fff0428 3ffee9c8 40204568
3ffffe60: 00000120 3ffffec0 3ffffec0 4020627f
3ffffe70: 40239c86 00000119 3ffffec0 402062cb
3ffffe80: 00000010 00000010 3ffffec0 402062fd
3ffffe90: 00000001 00000001 3ffffec0 4020634a
3ffffea0: 40239c86 3fffff00 3fffff00 4020627f
3ffffeb0: 3ffeec2c 00000238 3ffefb0c 402024e2
3ffffec0: 3fff05a4 0000011f 00000119 401006dc
3ffffed0: 00000001 40203abc 3ffefb0c 40207032
3ffffee0: 00000000 3fffdad0 3ffefb0c 4020455e
3ffffef0: 3ffefb0c 3ffeea0c 3fffff20 4020459a
3fffff00: 00000000 00000000 00000000 4020642c
3fffff10: 3ffefb0c 3ffeea0c 3ffee9c8 40204621
3fffff20: 3fff040c 0000000f 00000001 00000000
3fffff30: 00000000 4bc6a7f0 0000d08b 3ffeebb4
3fffff40: 3ffeea0c 00000001 3ffe8504 3ffee9f0
3fffff50: 00000001 00000000 40203abc 0000000f
3fffff60: 00000000 3fff01e4 3ffee9c8 3ffeebb4
3fffff70: 00000001 3ffee9f0 3ffee9c8 40204870
3fffff80: 402073a0 00000000 00001388 40202504
3fffff90: 00000000 3fff01e4 00000000 feefeffe
3fffffa0: 3fffdad0 00000000 3ffeebac 402026b8
3fffffb0: 3fffdad0 00000000 3ffeebac 40206d70
3fffffc0: feefeffe feefeffe 3ffe8504 40100739
<<<stack<<<
ets Jan 8 2013,rst cause:2, boot mode:(1,7)
ets Jan 8 2013,rst cause:4, boot mode:(1,7)
wdt reset
你是指上文的程式碼無法執行嗎?我測試沒問題欸…
thanks,
jeffrey
上面那一串是執行13章的 “”WiFi_HTML 範例”” 後,在序列埠看到的亂碼
這個亂碼只有從Mac系統上用Arduino IDE燒錄程式碼,並且從網頁連線到ESP8266後才會出現
如果程式上傳完成沒有用網頁連線到ESP8266,序列埠不會出現這段亂碼
但是我用Windows上傳程式碼就可以順利連線到ESP8266的網頁,這實在神奇了
雖然這個問題暫時解決的,但不知道原因次什麼還又有點毛毛的
不過我也不知道要從哪個環節開始檢討,所以沒辦法自己找出問題的原因
_________________________________
我用 maxOS(10.14) 和 Windows 10(17134.345)
開啟Arduino IDE(1.8.7) 連線到網路硬碟上的範例程式(開啟相同檔案)
_________________________________
感謝告知!我現在沒有用Mac,也許這個月底會在Mac上測試看看。
thanks,
jeffrey
請問老師:
如果想要讓多片開發版互相用WiFi方式溝通的話,用什麼方式會比較好呢?
目前書上的解決方案有
1.用網頁對開發板下命令對板子上的元件做事
2.開發版透過網頁向使用者送出提示訊息
有沒有什麼方式是可以讓A板接收訊號,然後直接叫B板做事的方法呢?
你可以把A板當作前端,B板當作後端,B板最好採用樹莓派之類的高階控制板,才能負擔多個前端同時連線。
thanks,
jeffrey
請問關於”超圖解ESP32深度實”中的第六章,使用到SPIFFS工具
安裝了Arduino 2.0 IDE卻無法使用,網路上搜尋了一下,發現應該是Arduino IDE改版導致
請問有其他方式嗎?
沒錯,Sketch Data Upload外掛是用Java語言開發的,2.0版IDE不支援,所以我仍保留使用1.8.19版。
可以請教一下,如果同時安裝1.8跟2.0的IDE,系統會不會產生衝突?
因為發現很多工具在2.0都還不支援
不會,應用程式本體分別存於不同路徑,程式庫和編譯環境的工具鏈(例如:ESP32)是共用的。第一次啟動IDE 2.0時,它提示”AVR平台已安裝,無法安裝”錯誤,因為我的1.8.19版IDE的AVR工具鏈是舊版,需要在2.0的開發板管理員手動選擇最新版、安裝。
感謝!!我安裝後,使用visual code當編輯器成功:)
不過編譯過程跑出了一個警告訊號,我觀察了一下自己python版本是3.6.8
gen_esp32part.py:507: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode – interpreting them as being unequal
當然這個警告訊息不會導致編譯跟上傳的問題,您有遇過嗎?有辦法解決嗎?
沒有,我下週重新安裝試試。
請問SPIFFS Error: esptool not found!
如何解決,已使用1.8.19版IDE
ESP8266的esptool.py所在路徑:
請嘗試先在「開發板管理員」刪除ESP8266開發環境,或者手動刪除這個路徑裡的esp8266資料夾:
然後在開發板管理員重新安裝”ESP8266″開發環境試試。