ESP8266 Arduino IDE 開發問與答 Q&A ( 2 )
ESP8266 模組內,外接的 SPI Flash 大小為 4MBytes ( 下圖五個區間總共 ),規劃裡面的 1MBytes 為 SPIFFS ( SPI Flash File System ),SPIFFS (SPI Flash File Systm) 這一段區域是規劃用來給使用者存放檔案的
ESP8266 core for Arduino 的 Flash 規劃 (如藍色標示),#M SPIFFS 由這三部分構成:
#M SPIFFS= SPIFFS + EEPROM + System Param
上面有兩個 SPIFFS,這是怎麼回事 !?? ( 上圖中原作者應該是漏了 SPIFFS 的 S )
#M SPIFFS 是在 Arduino IDE 上面所設定的 "Flash Size: " 的參數 (假設使用 "4M (1M SPIFFS",那麼 # 就是 1),但實際上並沒有到 1M Bytes 那麼多的空間可以用(上圖右邊三塊)
扣除掉 EEPROM ( 4K Bytes ) 和 System Param ( 16K Bytes )和 SPIFFS 函式庫的計算,實際上只有少於 1004 K Bytes 的空間可以用,這裡的少於 1004 K Bytes 就算是裡面的 SPIFFS,也代表 SPIFFS 函式庫裡面可用的空間範圍
所以 #M SPIFFS 是燒錄時設定的 "Flash Size:",算式裡面的 SPIFFS 是函式庫可使用的 Flash 範圍。
因為名稱一樣,所以為避免搞混,下面使用時都是使用全文:#M SPIFFS 和 SPIFFS,請記住它們兩者的差別 !
========================================================================
接著說明 EEPROM。要先知道的是:ESP8266 沒有所謂的 EEPROM 這個東西!
這裡的 EEPROM 是從 Flash 中取一塊磁區虛擬出來的,最大只有 4KBytes (4096) 可以使用,位在 SPIFFS (也就是 File System) 的後面,Flash 倒數的第 5 個磁區( 一個磁區 4KBytes )。
而 System Param 則占據 Flash 最後四個磁區,共 16KBytes (1024*16 = 16384)。
EEPROM 和 System Param 這兩個區域佔據的位置不管 Flash 多大,都是固定大小與位置的。
SPIFFS 這個空間中,除了可以放置檔案之外,剩餘的空間還可以額外做使用,這就是上面說到的 "可自由使用的空間",這個空間裡的磁區大小、擦除、讀和寫的方式都是跟 EEPROM 和 SPIFFS 使用相同的 SPI Flash 基本操作函式就可以實現。
例如在這空間中,劃分一塊 256 KBytes 的空間來儲存個人通訊資料,只要 SPIFFS 扣除掉已使用的檔案大小還有足夠的空間,就可以自由規劃來使用。
所以知道哪裡是 "可自由使用的空間" 就能獲得更多的儲存資源可以做應用。
ESP8266 的 SPI Flash 讀取很簡單,只要知道讀取的位址與讀取的數量就可以;但是要寫入資料,則是必須要做磁區擦除的動作才能呼叫寫入的函式寫入資料,不然就會錯亂,WHY ???
簡單解釋就是:
擦除時將磁區裡面的資料全部弄為 1 ( 0x11111111),在寫入資料時 (例如 0x00001010) 進行 AND 計算 (得 0x00001010);如果不進行擦除就直接寫入的話,答案就不會是 0x00001010。
另外,Flash 是有壽命的 ! 大概是 100,000 ~ 1,000,000 才會失效 !
所以存放的數據就比較適合不經常改變的資料。
了解 SPI Flash 操作最好的例子,就是 EEPROM 函式庫 ! 雖然它的最大可使用的大小只有 4KBytes,但是它怎麼對 Flash 擦除、讀取和寫入卻是可以作為 SPI Flash 操作函式的學習與瞭解。
練習計算 EEPROM 的位址
SPI Flash 讀取時可以使用記憶體映射的方式讀取前面 1MByte 的位址,但是對於後面的部分必須要以真實的 Flash 位址才行。(此段話我不是很理解,後面的部分是指什麼?)
SPI Flash 在寫入時必須先進行擦除的動作,每次擦除以 4KBytes (一個磁區 (sector) ) 為單位
因為整個 SPI FLASH 有 4M Bytes (0x40 0000),往回扣除 System Param 的 16K Bytes (0x4000)再扣除 EEPROM 的 4K Bytes (0x1000) ,最後除上磁區的單位( 4K Bytes ) ,就算出 EEPROM 所處的磁區位址是第 1019 個磁區。
存取 SPIFFS
SPIFFS 存放資料的部分,應該是從 SPI Flash 3M 的地方開始放,在這個空間(約957, 314 bytes) 進行任何讀寫的動作。
必須特別注意資料的邊界,不可跨界動作 ! 一旦出錯就是系統重啟,掛機的意思 !
為了避免掛機,建議從尾部取用 ! 以現在使用的例子,就是從第 1019 個磁區( EEPROM 的頭)往前取用,每次取用以一個磁區做為最小單位,最大可取用到第 768 磁區。
原作者給岀了一個程式碼,取用 EEPROM 前面的一個磁區作為存放資料之用,每一筆資料的長度不超過 64 的大小,要存放 64 筆資料 (當然可以存放更多的資料,只要在增加擦除磁區的位置和增加寫入與讀取的數量就可以 ),寫入之後還要讀出資料做比對,來確認是否真的寫入完成 !
Hint : 原文作者在下方的程式碼中 addrstart 為 0x3EB000,但文字敘述是取前面一個磁區,應為誤植(0x3FB000 往前回推 4K Bytes 應是 0x3FA000)。
#include "ESP8266WiFi.h"
#include "ESP.h"
char text[] = "this is a test string";
char buff[ sizeof(text) ];
const uint32_t addrstart = 0x3FA000;
const uint32_t addrend = 0x3FB000;
void setup() {
Serial.begin(115200);
// ERASE 。 flashEraseSector 的輸入參數需為 "磁區" ,故將 addrstart/4096 ,也就是右移 12 位
if( !ESP.flashEraseSector( addrstart >> 12 ) ) {
Serial.println( "\n\nErase error\n");
return;
} else {
Serial.println( "\n\nErase OK\n----------------");
}
// WRITE
uint32_t flash_address;
for( int i = 0; i < 64; i++ ) {
flash_address = addrstart + i * 64;
if( !ESP.flashWrite( flash_address, (uint32_t*)text, sizeof(text) - 1 ) ) {
Serial.printf( "%2d: [error] write addr: %p\n", i, flash_address );
continue;
} else Serial.printf( "%2d: addr: %p write [%s] OK\n", i, flash_address, text );
memset( buff, 0, sizeof( buff ) );
if ( !ESP.flashRead( flash_address, (uint32_t*)buff, sizeof(text) - 1 ) ) {
printf( "%2d: [error] read addr: %p\n", i, flash_address );
continue;
} else Serial.printf( "%2d: addr: %p read [%s] OK\n", i, flash_address, buff );
if ( memcmp( text, buff, sizeof( buff ) ) != 0 ) {
printf( "%2d: addr: %p, In != Out\n", i, flash_address );
continue;
} else Serial.printf( "%2d: addr: %p compare OK\n", i, flash_address );
}
}
void loop() {
delay(500);
}
一個磁區有 4K Bytes(4096 Bytes) ,原作者將其在細分割為 64 個小區段,每個區段就會是 64 Bytes 的長度。
補充:傳數値
#include "ESP8266WiFi.h"
#include "ESP.h"
uint32_t text0 = 1;
uint32_t buff0;
char text[] = "Ephone";
char text1[] = "evan6060";
char buff[ sizeof(text) ];
char buff1[ sizeof(text1) ];
const uint32_t addrstart = 0x3FA000;
void setup() {
Serial.begin(115200);
// ERASE 。 flashEraseSector 的輸入參數需為 "磁區" ,故將 addrstart/4096 ,也就是右移 12 位
if( !ESP.flashEraseSector( addrstart >> 12 ) ) {
Serial.println( "\n\nErase error\n");
return;
} else {
Serial.println( "\n\nErase OK\n----------------");
}
// WRITE
uint32_t flash_address;
int i = 0;
flash_address = addrstart + i*64;
if( !ESP.flashWrite( flash_address, (uint32_t*)&text0, 32 ) ) {
Serial.printf( "%2d: [error] write addr: %p\n", i, flash_address );
} else Serial.printf( "%2d: addr: %p write [%d] OK\n", i, flash_address, text0 );
if ( !ESP.flashRead( flash_address, (uint32_t*)&buff0, 32 ) ) {
printf( "%2d: [error] read addr: %p\n", i, flash_address );
} else Serial.printf( "%2d: addr: %p read [%d] OK\n", i, flash_address, buff0 );
i = 1;
flash_address = addrstart + i*64;
if( !ESP.flashWrite( flash_address, (uint32_t*)text, 64 ) ) {
Serial.printf( "%2d: [error] write addr: %p\n", i, flash_address );
} else Serial.printf( "%2d: addr: %p write [%s] OK\n", i, flash_address, text );
memset( buff, 0, sizeof( buff ) );
if ( !ESP.flashRead( flash_address, (uint32_t*)buff, 64 ) ) {
printf( "%2d: [error] read addr: %p\n", i, flash_address );
} else Serial.printf( "%2d: addr: %p read [%s] OK\n", i, flash_address, buff );
if ( memcmp( text, buff, sizeof( buff ) ) != 0 ) {
printf( "%2d: addr: %p, In != Out\n", i, flash_address );
} else Serial.printf( "%2d: addr: %p compare OK\n", i, flash_address );
i = 2;
flash_address = addrstart + i*64;
if( !ESP.flashWrite( flash_address, (uint32_t*)text1, 64 ) ) {
Serial.printf( "%2d: [error] write addr: %p\n", i, flash_address );
} else Serial.printf( "%2d: addr: %p write [%s] OK\n", i, flash_address, text1 );
memset( buff1, 0, sizeof( buff1 ) );
if ( !ESP.flashRead( flash_address, (uint32_t*)buff1, 64 ) ) {
printf( "%2d: [error] read addr: %p\n", i, flash_address );
} else Serial.printf( "%2d: addr: %p read [%s] OK\n", i, flash_address, buff1 );
if ( memcmp( text1, buff1, sizeof( buff1 ) ) != 0 ) {
printf( "%2d: addr: %p, In != Out\n", i, flash_address );
} else Serial.printf( "%2d: addr: %p compare OK\n", i, flash_address );
}
void loop() {
delay(500);
}