ESP8266開發之旅 網絡篇⑬ SPIFFS——ESP8266 SPIFFS文件系統

授人以魚不如授人以漁,目的不是爲了教會你具體項目開發,而是學會學習的能力。但願你們分享給你周邊須要的朋友或者同窗,說不定大神成長之路有博哥的奠定石。。。css

QQ技術互動交流羣:ESP8266&32 物聯網開發 羣號622368884,不喜勿噴html

1、你若是想學基於Arduino的ESP8266開發技術

1、基礎篇java

  1. ESP8266開發之旅 基礎篇① 走進ESP8266的世界
  2. ESP8266開發之旅 基礎篇② 如何安裝ESP8266的Arduino開發環境
  3. ESP8266開發之旅 基礎篇③ ESP8266與Arduino的開發說明
  4. ESP8266開發之旅 基礎篇④ ESP8266與EEPROM
  5. ESP8266開發之旅 基礎篇⑤ ESP8266 SPI通訊和I2C通訊
  6. ESP8266開發之旅 基礎篇⑥ Ticker——ESP8266定時庫

2、網絡篇git

  1. ESP8266開發之旅 網絡篇① 認識一下Arduino Core For ESP8266
  2. ESP8266開發之旅 網絡篇② ESP8266 工做模式與ESP8266WiFi庫
  3. ESP8266開發之旅 網絡篇③ Soft-AP——ESP8266WiFiAP庫的使用
  4. ESP8266開發之旅 網絡篇④ Station——ESP8266WiFiSTA庫的使用
  5. ESP8266開發之旅 網絡篇⑤ Scan WiFi——ESP8266WiFiScan庫的使用
  6. ESP8266開發之旅 網絡篇⑥ ESP8266WiFiGeneric——基礎庫
  7. ESP8266開發之旅 網絡篇⑦ TCP Server & TCP Client
  8. ESP8266開發之旅 網絡篇⑧ SmartConfig——一鍵配網
  9. ESP8266開發之旅 網絡篇⑨ HttpClient——ESP8266HTTPClient庫的使用
  10. ESP8266開發之旅 網絡篇⑩ UDP服務
  11. ESP8266開發之旅 網絡篇⑪ WebServer——ESP8266WebServer庫的使用
  12. ESP8266開發之旅 網絡篇⑫ 域名服務——ESP8266mDNS庫
  13. ESP8266開發之旅 網絡篇⑬ SPIFFS——ESP8266 Flash文件系統
  14. ESP8266開發之旅 網絡篇⑭ web配網
  15. ESP8266開發之旅 網絡篇⑮ 真正的域名服務——DNSServer
  16. ESP8266開發之旅 網絡篇⑯ 無線更新——OTA固件更新

3、應用篇github

  1. ESP8266開發之旅 應用篇① 局域網應用 ——炫酷RGB彩燈
  2. ESP8266開發之旅 應用篇② OLED顯示天氣屏
  3. ESP8266開發之旅 應用篇③ 簡易版WiFi小車

4、高級篇web

  1. ESP8266開發之旅 進階篇① 代碼優化 —— ESP8266內存管理
  2. ESP8266開發之旅 進階篇② 閒聊Arduino IDE For ESP8266配置
  3. ESP8266開發之旅 進階篇③ 閒聊 ESP8266 Flash
  4. ESP8266開發之旅 進階篇④ 常見問題 —— 解決困擾
  5. ESP8266開發之旅 進階篇⑤ 代碼規範 —— 像寫文章同樣優美
  6. ESP8266開發之旅 進階篇⑥ ESP-specific APIs說明

1. 前言

    在前面博文關於ESP8266WiFiWebServer的例程中,你們能夠發現,博主基本上都是手動拼裝html內容返回,html的內容被固定寫在咱們的Arduino ESP代碼中。
    那麼這樣就有兩點弊端:服務器

  1. ESP8266代碼至關臃腫
  • 爲了開發方便,web server網頁除了自身的html內容以外,還包括一些css文件,甚至引入了JQuery庫以及一些圖片相關資源。若是把這些內容也直接寫入到ESP8266代碼中,會致使8266總體代碼變大,甚至可能超過flash規定的大小;
  1. 業務職責分離不明確
  • 通常來講,在一個開發團隊中,有人負責開發ESP8266業務需求,有人負責開發WebServer網頁內容,有人負責硬件部分。直接把html的內容直接寫入到ESP8266代碼中,就會致使業務職責混亂,而且若是要修改html內容的時候還得一個個改掉arduino的文件,也有可能改錯標識符之類的。理想狀況應該是,只須要更新web server的html文件就好,原來的esp8266 arduino邏輯不用更新;

    基於以上兩點弊端,正式引入本篇章須要研究的ESP8266 文件系統(SPI Flash FileSystem,簡稱爲SPIFFS)。網絡

    先來看一個概念圖:ide

image

    這個文件系統能夠幫助咱們存儲一些變動頻率不頻繁的文件例如網頁、配置或者是某些固化的數據等。
    其實,咱們用得更多的是存儲網頁,將網頁和相關資源(如:圖片、html、css、javaScript)存入到flash的SPIFFS區域。
    原理以下圖:
image函數

2. FLASH存儲分配

    在講解SPIFFS以前,咱們來看看在Arduino環境下ESP8266的flash存儲分配,請看下圖:
image

    具體能夠分爲幾部分:

  1. 代碼區
  • 又叫作程序存儲區,其中又區分爲當前代碼區(current Sketch),更新代碼區(OTA update);
  1. 文件系統
  • 這個就是咱們這節重點講解的SPI Flash File System,簡稱SPIFFS閃存文件系統。
  • 即便文件系統與程序存儲在同一個閃存芯片上,燒入新的代碼也不會修改文件系統內容。這容許使用文件系統來存儲Web服務器的代碼數據、配置文件或內容。而這個SPIFFS文件系統的大小能夠經過燒寫環境來配置,目前通常有1M,2M,3M等等。博主建議若是是NodeMcu板子,能夠配置成3M

  • 爲了使用文件系統,須要把下面的頭文件包含在代碼中:

#include <FS.h>
  1. EEPROM
  • 具體講解請回顧 ESP8266開發之旅 基礎篇④ ESP8266與EEPROM
  1. WiFi Config
  • 這個區域就是咱們設置WiFi模塊配置的時候存儲的數據。

3. SPIFFS文件系統

3.1 文件系統限制

    ESP8266的文件系統實現必須知足芯片的限制,其中最重要是有限的RAM。SPIFFS之因此被ESP8266選擇做爲文件系統,是由於它是爲小型系統專門設計的,同時是以一些簡化和限制爲代價的。
    首先,SPIFFS不支持目錄,它只存儲一個「扁平化」的文件列表。可是與傳統的文件系統相反,斜槓字符「/」在文件名中是容許的,所以處理目錄列表的函數(例如,openDir("/website"))基本上只是過濾文件名,並保留之前綴(/website/)開始的那些文件。
    而後,對於文件名,總共有32個字符限制。一個「\0」字符被保留用於c字符串終止符,所以留給咱們31個可用字符長度。
    綜合起來,這意味着建議保持短文件名,不要使用深嵌套的目錄,由於每一個文件的完整路徑(包括目錄、「/」字符、基本名稱、點和擴展名)最多隻能是31個字符長度。例如,/website/images/bird_thumbnail.jpg 達到了34個字符長度,若是使用它,將致使一些問題。
    警告:這個限制很容易達到,若是忽略,問題可能會被忽略,由於在編譯和運行時不會出現錯誤信息。

3.2 文件系統文件添加方式

    使用文件系統目的就是爲了存儲文件,那麼存儲文件的方式其實能夠分爲3種:

  • 直接代碼中調用FS提供的API在SPIFFS上建立文件;
  • 經過 ESP8266FS 工具把文件上傳到SPIFFS;
  • 經過OTA Update的方式上傳到SPIFFS;

    本質上,不管是經過ESP8266FS或者OTA Update的方式把文件上傳到SPIFFS,其底層都是經過調用FS提供的API去完成,因此咱們只須要了解FS經常使用API便可

4. SPIFFS庫

    瞭解一下SPIFFS文件系統經常使用的操做方法,如下是博主總結的百度腦圖:

image

    方法分爲3大類:

  1. SPIFFS專用方法
  2. Dir對象專用方法
  3. File對象專用方法

4.1 SPIFFS專用方法

4.1.1 begin —— 掛載SPIFFS文件系統

函數說明:

/**
 * 掛載SPIFFS文件系統
 * @return  bool  若是文件系統掛載成功,返回true,不然返回false
 */
bool begin();

注意點:

  • 它必須在其餘任何FS API被調用以前先調用;
  • Arduino IDE配置時須要啓用SPIFFS;

4.1.2 format —— 格式化文件系統

函數說明:

/**
 * 格式化文件系統
 * @return  bool 若是格式化成功則返回true
 */
bool format();

注意點:

  • 能夠在執行begin()以前或者以後調用

4.1.3 open —— 打開文件

函數說明:

/**
 * 打開文件,某種模式下會建立文件
 * @param path 文件路徑
 * @param mode 存取模式
 * @return File 返回一個File對象
 */
File open(const char* path, const char* mode);
File open(const String& path, const char* mode);

注意點:

  1. 路徑必須是以斜線開頭的絕對路徑(如:/dir/filename.txt);
  2. 模式參數是個用字符串指定的存取模式,其值爲「r」、「w」、「a」、「r+」、「w+」和「a+」之中的一個。
  • r 以只讀方式操做文件,讀位置在文件的開始位置,文件不存在返回空對象;
  • r+ 以可讀可寫方式打開文件,讀寫位置在文件的開始位置,文件不存在返回空對象;
  • w 截取文件長度到0或者建立新文件,只能寫操做,寫位置在文件的開始位置;
  • w+ 截取文件長度到0或者建立新文件,可讀可寫操做,寫位置在文件的開始位置;
  • a 在文件末尾追加內容或者文件不存在就建立新文件,追加位置在當前文件的末尾,只能寫操做
  • a+ 在文件末尾追加內容或者文件不存在就建立新文件,追加位置在當前文件的末尾,可讀寫操做

若是要檢查文件是否打開成功,請使用如下代碼:

File f = SPIFFS.open("/f.txt", "w");
if (!f) {
    Serial.println("file open failed");
}

4.1.4 exists —— 路徑是否存在

函數說明:

/**
 * 路徑是否存在
 * @param path 文件路徑
 * @return bool 若是指定的路徑存在,則返回true,不然返回false
 */
bool exists(const char* path);
bool exists(const String& path);

4.1.5 openDir —— 打開絕對路徑文件夾

函數說明:

/**
 * 打開絕對路徑文件夾
 * @param path 文件路徑
 * @return Dir 打開絕對路徑文件夾,返回一個Dir對象
 */
Dir openDir(const char* path);
Dir openDir(const String& path);

4.1.6 remove —— 刪除絕對路徑的文件

函數說明:

/**
 * 刪除絕對路徑的文件
 * @param path 文件路徑
 * @return bool 若是刪除成功則返回true,不然返回false
 */
bool remove(const char* path);
bool remove(const String& path);

4.1.7 rename —— 從新命名文件

函數說明:

/**
 * 從新命名文件
 * @param pathFrom 原始路徑文件名
 * @param pathTo   新路徑文件名
 * @return bool 若是從新命名成功則返回true,不然返回fals
 */
bool rename(const char* pathFrom, const char* pathTo);
bool rename(const String& pathFrom, const String& pathTo);

4.1.8 info —— 獲取文件系統的信息

函數說明:

/**
 * 獲取文件系統的信息,存儲在FSInfo對象
 * @param info FSInfo對象
 * @return bool 是否獲取成功
 */
bool info(FSInfo& info);

FSInfo定義以下:

struct FSInfo {
    size_t totalBytes;//整個文件系統的大小
    size_t usedBytes;//文件系統全部文件佔用的大小
    size_t blockSize;//SPIFFS塊大小
    size_t pageSize;//SPIFFS邏輯頁數大小
    size_t maxOpenFiles;//可以同時打開的文件最大個數
    size_t maxPathLength;//文件名最大長度(包括一個字節的字符串結束符)
};

4.2 Dir對象專用方法

在上面的方法中,咱們能夠獲取到Dir對象,那麼看看Dir對象定義是什麼?

class Dir {
public:
    Dir(DirImplPtr impl = DirImplPtr()): _impl(impl) { }

    File openFile(const char* mode);//打開文件
    String fileName();//獲取文件名字
    size_t fileSize();//文件大小
    bool next();//下一個文件

protected:
    DirImplPtr _impl;
};

注意點:

  • Dir對象的做用主要是遍歷文件夾裏的全部文件;
  • 文件夾並非真正意義上的文件夾,文件都是平鋪的;

4.2.1 openFile —— 打開文件

函數說明:

/**
 * 打開文件
 * @param mode 打開模式,請參考open方法
 * @return File 返回一個File對象
 */
File openFile(const char* mode);

4.2.2 fileName —— 獲取文件名字

函數說明:

/**
 * 獲取文件大小
 * @return size_t 文件大小
 */
size_t fileSize();

4.2.3 next —— 是否還有下一個文件

函數說明:

/**
 * 是否還有下一個文件
 * @return bool true 表示還有文件
 */
bool next();

注意點:

  • 其實這裏用到了遍歷;
  • 只要還有文件,dir.next()就會返回true,這個方法必須在fileName()和openFile()方法以前調用。

4.3 File對象專用方法

那麼,咱們來看看File對象結構:

class File : public Stream
{
public:
    File(FileImplPtr p = FileImplPtr()) : _p(p) {}

    // Print methods:
    size_t write(uint8_t) override;
    size_t write(const uint8_t *buf, size_t size) override;

    // Stream methods:
    int available() override;
    int read() override;
    int peek() override;
    void flush() override;
    size_t readBytes(char *buffer, size_t length)  override {
        return read((uint8_t*)buffer, length);
    }
    size_t read(uint8_t* buf, size_t size);
    bool seek(uint32_t pos, SeekMode mode);
    bool seek(uint32_t pos) {
        return seek(pos, SeekSet);
    }
    size_t position() const;
    size_t size() const;
    void close();
    operator bool() const;
    const char* name() const;

protected:
    FileImplPtr _p;
};

File對象支持Stream的全部方法,所以可使用readBytes、findUntil、parseInt、printIn以及其餘stream方法。如下是File對象特有的一些方法:

4.3.1 seek —— 文件偏移位置

函數說明:

/**
 * 設置文件位置偏移
 * @param pos 偏移量
 * @param mode 偏移模式
 * @return bool 若是移動成功,則返回true,不然返回false
 */
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
     return seek(pos, SeekSet);
}

注意點:

  • 若是模式值是 SeekSet,則從文件開頭移動指定的偏移量。
  • 若是模式值是 SeekCur,則從目前的文件位置移動指定的偏移量。
  • 若是模式值是 SeekEnd,則從文件結尾處移動指定的偏移量。

4.3.2 position —— 返回目前在文件中的位置

函數說明:

/**
 * 返回目前在文件中的位置
 * @return size_t 當前位置
 */
size_t position();

4.3.3 size —— 返回文件大小

函數說明:

/**
 * 返回文件大小
 * @return size_t 文件大小
 */
size_t size();

4.3.4 name —— 返回文件名字

函數說明:

/**
 * 返回文件名字
 * @return const char* 文件名字
 */
const char* name();

4.3.5 close —— 關閉文件

函數說明:

/**
 * 關閉文件
 */
void close();

注意點:

  • 執行這個方法以後,就不能在該文件上執行其餘操做。

5. 實例

5.1 文件操做

實例說明

spiffs文件操做常見方法使用,包括文件查找、建立、打開、關閉、刪除

實例源碼

/**
 * 功能描述:spiffs文件操做常見方法使用,包括文件查找、建立、打開、關閉、刪除
 */
#include <FS.h>

//如下三個定義爲調試定義
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

#define myFileName  "mydemo.txt"

void setup(){
  DebugBegin(9600);
  DebugPrintln("Check Start SPIFFS...");
  //啓動SPIFFS,若是下載配置沒有配置SPIFFS,返回false
  if(!SPIFFS.begin()){
     DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
     return;
  }
  DebugPrintln("Start SPIFFS Done.");
  //判斷文件是否存在
  if(SPIFFS.exists(myFileName)){
    DebugPrintln("mydemo.txt exists.");
  }else{
    DebugPrintln("mydemo.txt not exists.");
  }
  
  File myFile;
  //打開文件 不存在就建立一個 可讀可寫
  myFile = SPIFFS.open(myFileName,"w+");
  //關閉文件
  myFile.close();
  //再次判斷文件是否存在
  if(SPIFFS.exists(myFileName)){
    DebugPrintln("mydemo.txt exists.");
  }else{
    DebugPrintln("mydemo.txt not exists.");
  }
  //刪除文件
  DebugPrintln("mydemo.txt removing...");
  SPIFFS.remove(myFileName);
  //再次判斷文件是否存在
  if(SPIFFS.exists(myFileName)){
    DebugPrintln("mydemo.txt exists.");
  }else{
    DebugPrintln("mydemo.txt not exists.");
  }
}

void loop(){
}

實驗結果
image

5.2 文件列表

實例說明

查看spiffs文件系統列表

實例準備

  • NodeMcu開發板
  • 燒錄配置須要開啓SPIFFS

實例源碼

/**
 * 功能描述:查看spiffs文件系統列表
 */
#include <FS.h>

//如下三個定義爲調試定義
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

void setup(){
  DebugBegin(9600);
  DebugPrintln("Check Start SPIFFS...");
  //啓動SPIFFS,若是下載配置沒有配置SPIFFS,返回false
  if(!SPIFFS.begin()){
     DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
   return;
  }
  DebugPrintln("Start SPIFFS Done.");
  
  File myFile;
  //打開文件 不存在就建立一個 可讀可寫
  myFile = SPIFFS.open("/myDemo.txt","w+");
  //關閉文件
  myFile.close();
  
  //打開文件 不存在就建立一個 可讀可寫
  myFile = SPIFFS.open("/myDemo.jpg","w+");
  //關閉文件
  myFile.close();
  
    //打開文件 不存在就建立一個 可讀可寫
  myFile = SPIFFS.open("/myDemo.html","w+");
  //關閉文件
  myFile.close();
  
  Dir dir = SPIFFS.openDir("/");
  while(dir.next()){
    String fileName = dir.fileName();
  size_t fileSize = dir.fileSize();
  Serial.printf("FS File:%s,size:%d\n",fileName.c_str(),fileSize);
  }
  DebugPrintln("Setup Done!");
}

void loop(){
}

實驗結果
image

5.3 文件讀寫

實例說明

往文件myDemo.txt中寫入「單片機菜鳥博哥666」並讀取出來顯示。

實例源碼

/**
 * 功能描述:演示文件讀寫功能
 */
#include <FS.h>

//如下三個定義爲調試定義
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

void setup(){
  DebugBegin(9600);
  DebugPrintln("Check Start SPIFFS...");
  //啓動SPIFFS,若是下載配置沒有配置SPIFFS,返回false
  if(!SPIFFS.begin()){
     DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
   return;
  }
  DebugPrintln("Start SPIFFS Done.");
  
  File myFile;
  //打開文件 不存在就建立一個 可讀可寫
  myFile = SPIFFS.open("myDemo.txt","w+");
  if(myFile){
    DebugPrintln("Writing something to myDemo.txt...");
  myFile.println("單片機菜鳥博哥666");
  myFile.close();
  DebugPrintln("Writing Done.");
  }else{
    DebugPrintln("Open File Failed.");
  }
  
  //打開文件 可讀
  myFile = SPIFFS.open("myDemo.txt","r");
  if(myFile){
    DebugPrintln("Reading myDemo.txt...");
  while(myFile.available()){
    //讀取文件輸出
    Serial.write(myFile.read());
  }
  myFile.close();
  }else{
    DebugPrintln("Open File Failed.");
  }
  
  DebugPrintln("Setup Done!");
}

void loop(){
}

實驗結果
image

5.4 燒寫文件

實驗說明

    在上面的例子中,咱們都是本身手動在SPIFFS文件系統中建立或者寫入文件,可是對於習慣web開發的人員來講,確定是直接把寫好的web程序(html、css、js、資源文件等)直接燒入文件系統更加使人容易接受。因此本例子主要是講解如何往SPIFFS裏面燒寫文件。
    這個例子是重點,由於絕大部分的web開發(web配網、web頁面等)都是經常使用燒寫文件的方式,請讀者仔細閱讀。

    要存入SPIFFS區域的文件,都得事先放在代碼目錄裏的「data」目錄(請自行新增「data」目錄)。
    例如,存在一個項目工程叫作espStaticWeb,其文件結構以下:

image

    負責將文件上傳到SPIFFS的工具叫作 ESP8266FS。ESP8266FS是一個集成到Arduino IDE中的工具,它將一個菜單項添加到工具菜單,用於將skench data目錄的內容上傳到ESP8266 Flash文件系統中。
    這個工具須要另外安裝,整個上傳文件步驟以下:

  1. 下載 ESP8266FS工具
  2. 將下載到的文件解壓到Arduino IDE安裝路徑下的tools文件夾(若是不存在這個文件夾,請自行增長)。參考下圖:

image

  1. 重啓Arduino IDE
  2. 打開一個Sketch工程(新建或者打開最近的工程),去到Sketch工程目錄下建立一個data目錄(不存在該目錄),而後把你須要放到文件系統的文件copy到這裏。
  3. 確保你選擇了正確的板子、com口,關閉掉串口監視器。
  4. 選擇 工具 ESP8266 Sketch Data Upload

image

而後就會開始上傳文件到ESP8266 flash文件系統。

image

當IDE顯示「SPIFFS Image Uploaded」,表明上傳完畢。
image

    那麼接下來講明一下本例子內容:

  • 往8266 SPIFFS文件系統中上傳一個config.txt文件(請讀者自行建立,而後放在data目錄,上傳到ESP8266),而後讀取出來。文件內容包括:
{"name":"esp8266","flash":"QIO","board":"NodeMcu"}

實驗準備

  1. 往8266 SPIFFS文件系統中上傳一個config.txt文件(請讀者自行建立,而後放在data目錄,上傳到ESP8266)
  2. NodeMcu開發板

實驗源碼

/**
 * 功能描述:演示上傳文件並讀取文件內容
 * 前提:須要先往SPIFFS裏面上傳config.txt文件 
 */
#include <FS.h>

//如下三個定義爲調試定義
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

void setup(){
  DebugBegin(9600);
  DebugPrintln("Check Start SPIFFS...");
  //啓動SPIFFS,若是下載配置沒有配置SPIFFS,返回false
  if(!SPIFFS.begin()){
     DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
   return;
  }
  DebugPrintln("Start SPIFFS Done.");
  
  File myFile;
  //打開文件 不存在就建立一個 可讀可寫
  myFile = SPIFFS.open("/config.txt","r");
  if(myFile){
    //打印文件大小
    int size = myFile.size();
  Serial.printf("Size=%d\r\n", size);
  //讀取文件內容
  DebugPrintln(myFile.readString());
  myFile.close();
  DebugPrintln("Reading Done.");
  }else{
    DebugPrintln("Open File Failed.");
  }
}

void loop(){
}

實驗結果
image

6. 總結

SPIFFS文件系統屬於很是重要的一篇,但願讀者能夠認真理解使用。

相關文章
相關標籤/搜索