授人以魚不如授人以漁,目的不是爲了教會你具體項目開發,而是學會學習的能力。但願你們分享給你周邊須要的朋友或者同窗,說不定大神成長之路有博哥的奠定石。。。git
QQ技術互動交流羣:ESP8266&32 物聯網開發 羣號622368884,不喜勿噴github
1、基礎篇web
2、網絡篇編程
- ESP8266開發之旅 網絡篇① 認識一下Arduino Core For ESP8266
- ESP8266開發之旅 網絡篇② ESP8266 工做模式與ESP8266WiFi庫
- ESP8266開發之旅 網絡篇③ Soft-AP——ESP8266WiFiAP庫的使用
- ESP8266開發之旅 網絡篇④ Station——ESP8266WiFiSTA庫的使用
- ESP8266開發之旅 網絡篇⑤ Scan WiFi——ESP8266WiFiScan庫的使用
- ESP8266開發之旅 網絡篇⑥ ESP8266WiFiGeneric——基礎庫
- ESP8266開發之旅 網絡篇⑦ TCP Server & TCP Client
- ESP8266開發之旅 網絡篇⑧ SmartConfig——一鍵配網
- ESP8266開發之旅 網絡篇⑨ HttpClient——ESP8266HTTPClient庫的使用
- ESP8266開發之旅 網絡篇⑩ UDP服務
- ESP8266開發之旅 網絡篇⑪ WebServer——ESP8266WebServer庫的使用
- ESP8266開發之旅 網絡篇⑫ 域名服務——ESP8266mDNS庫
- ESP8266開發之旅 網絡篇⑬ SPIFFS——ESP8266 Flash文件系統
- ESP8266開發之旅 網絡篇⑭ web配網
- ESP8266開發之旅 網絡篇⑮ 真正的域名服務——DNSServer
- ESP8266開發之旅 網絡篇⑯ 無線更新——OTA固件更新
3、應用篇網絡
4、高級篇app
EEPROM(Electrically Erasable Programmable Read-Only Memory),電可擦可編程只讀存儲器——一種掉電後數據不丟失的存儲芯片。
EEPROM能夠在不使用文件和文件系統的狀況下用來固化一些數據,常見的好比用來保存SSID或者Password,保存用戶設置等數據,這樣就能夠不用每次都經過燒寫程序來改變系統運行時的初始值。
Arduino提供了完善的eeprom庫,不過須要注意的是ESP8266沒有硬件EEPROM,使用的是flash模擬的EEPROM。dom
EEPROM庫在Arduino中常常用於存儲設定數據。固然基於Arduino的ESP8266也不例外。可是,和真正的Arduino板子不同的是,ESP8266採用的方式是將flash中某一塊4K的存儲模擬成EEPROM。至於爲何是4K呢?主要緣由是flash是以sector爲一個單位,1 sector等於4096Bytes(4KB),操做flash時是以sector爲一個總體來操做。
讀取操做是經過ESP8266 SDK提供的API將flash中的內容讀取到Buffer中是沒有限制一次就要將4K全讀完,Buffer的大小由EEPROM.begin(size)決定,可是因爲Buffer大小會佔用內存RAM,因此務必按照實際須要來定義大小。
寫入操做是經過commit將flash eeprom地址的4K 存儲內容刪除後纔將Buffer寫入flash中(也就是說就算你buffer只有4個字節,可是最終仍是會刷新整個sector),原理大體以下圖:
因此要確保內容被保存到flash中,須要考慮commit的時機。函數
下圖來源於Arduino For ESP8266對於EEPROM的介紹:
具體意思能夠理解爲如下幾點:oop
EEPROM庫很是簡單,請看博主總結的百度腦圖:
僅僅有5個方法,可是博主在這裏仍是帶讀者深刻去理解一下它們。
Arduino Core For ESP8266的源碼在github上能夠查找到,讀者能夠把它下載下來以便後續深刻開發,連接位置爲 ESP8266源碼。
而後請找到下圖位置:
性能
該功能用於申請具體大小的ram內存空間。
函數: begin(size)
參數:
size:要申請的內存大小。
返回值: 無;
注意點:
void EEPROMClass::begin(size_t size) { //size 必須大於0 if (size <= 0) return; if (size > SPI_FLASH_SEC_SIZE) //超過4096的size,都強制變成4096 size = SPI_FLASH_SEC_SIZE; //size最終的大小都是4個倍數,好比輸入1,最終size是4 size = (size + 3) & (~3); //In case begin() is called a 2nd+ time, don't reallocate if size is the same if(_data && size != _size) { delete[] _data; _data = new uint8_t[size]; } else if(!_data) { //建立內存buffer空間 這裏須要注意 _data = new uint8_t[size]; } _size = size; noInterrupts(); //把具體內容讀取出來 spi_flash_read(_sector * SPI_FLASH_SEC_SIZE, reinterpret_cast<uint32_t*>(_data), _size); interrupts(); _dirty = false; //make sure dirty is cleared in case begin() is called 2nd+ time }
該功能用於往內存空間去寫入數據。
函數: write(address,value)
參數:
address:要寫入的地址位置,取值範圍爲內存空間的地址0~size。
val:寫入的數據。
返回值: 無;
注意點:
void EEPROMClass::write(int const address, uint8_t const value) { if (address < 0 || (size_t)address >= _size) return; if(!_data) return; // Optimise _dirty. Only flagged if data written is different. uint8_t* pData = &_data[address]; if (*pData != value) { *pData = value; _dirty = true; } }
從源碼能夠看出,寫入的數據只是寫入到申請的內存空間,並非馬上寫入到flash中。
該功能用於讀取數據操做。
函數: read(address)
參數:
address:要讀取的地址位置,取值範圍爲內存空間的地址0~size。
返回值: 返回存儲數據;
注意點:
uint8_t EEPROMClass::read(int const address) { if (address < 0 || (size_t)address >= _size) return 0; if(!_data) return 0; //讀取內存數據 return _data[address]; }
從源碼看出,讀取的數據也是從begin中生成的內存空間中去獲取,並不會直接操做flash。操做內存的一個好處就是快。
該功能用於把內存空間的數據覆蓋到flash eeprom塊去。
函數: commit()
參數: 無;
返回值: 返回bool值,表示是否覆蓋成功;
注意點:
bool EEPROMClass::commit() { bool ret = false; if (!_size) return false; if(!_dirty) return true; if(!_data) return false; noInterrupts(); //是否擦除eeprom sector成功 if(spi_flash_erase_sector(_sector) == SPI_FLASH_RESULT_OK) { //把內存空間數據寫入到eeprom sector if(spi_flash_write(_sector * SPI_FLASH_SEC_SIZE, reinterpret_cast<uint32_t*>(_data), _size) == SPI_FLASH_RESULT_OK) { _dirty = false; ret = true; } } interrupts(); return ret; }
該功能用於寫入flash,而且釋放內存空間。
函數: end()
參數: 無;
返回值: 無;
注意點:
void EEPROMClass::end() { if (!_size) return; //寫入flash commit(); if(_data) { //回收內存空間 delete[] _data; } _data = 0; _size = 0; _dirty = false; }
前面,咱們說到,ESP8266採用的方式是將flash中某一塊4K的存儲模擬成EEPROM。那麼它到底在哪個位置呢?請看看源碼:
EEPROMClass::EEPROMClass(uint32_t sector) : _sector(sector) , _data(0) , _size(0) , _dirty(false) { } EEPROMClass::EEPROMClass(void) : _sector((((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE)) , _data(0) , _size(0) , _dirty(false) { }
/* Flash Split for 4M chips */ /* sketch @0x40200000 (~1019KB) (1044464B) */ /* empty @0x402FEFF0 (~4KB) (4112B) */ /* spiffs @0x40300000 (~3052KB) (3125248B) */ /* eeprom @0x405FB000 (4KB) */ /* rfcal @0x405FC000 (4KB) */ /* wifi @0x405FD000 (12KB) */ MEMORY { dport0_0_seg : org = 0x3FF00000, len = 0x10 dram0_0_seg : org = 0x3FFE8000, len = 0x14000 iram1_0_seg : org = 0x40100000, len = 0x8000 irom0_0_seg : org = 0x40201010, len = 0xfeff0 } PROVIDE ( _SPIFFS_start = 0x40300000 ); PROVIDE ( _SPIFFS_end = 0x405FB000 ); PROVIDE ( _SPIFFS_page = 0x100 ); PROVIDE ( _SPIFFS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld"
代入上面的公式變成:
EEPROMClass EEPROM(((0x405FB000 - 0x40200000) / SPI_FLASH_SEC_SIZE));
其中 SPI_FLASH_SEC_SIZE定位爲 4096(4K),具體定義可參考 spi_flash.h。
因此最終獲得的結果是:
EEPROMClass EEPROM(1019);
從上一節的計算,咱們能夠知道,根據計算公式,咱們會最終獲得一個具體位置的sector來描述eeprom。那麼,反過來思考一下,既然官方的eeprom對應的sector地址是SPIFFS_END的下一個sector,那麼在官方eeprom存儲不夠用的前提下,咱們是否能夠本身定義一個sector來繼續存儲更多的內容?若是能夠,那麼這個sector該取哪一部分呢?
很顯然,若是咱們沒有用到SPIFFS,徹底能夠利用這一塊區域去作咱們自定義的EEPROM。這裏咱們選擇SPIFFS的最後一個sector(爲何咱們會選擇它?留給讀者思考)。
按照公式倒推回去:
EEPROMClass EEPROM(1019 - 1);
EEPROMClass EEPROM(((0x405FB000 - 0x40200000) / SPI_FLASH_SEC_SIZE) - 1);
最終獲得咱們須要的自定義公式:
EEPROMClass EEPROM((((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE) - 1);
注意點:
/* * 功能描述:該代碼向EEPROM寫入100字節數據 */ #include <EEPROM.h> int addr = 0; //EEPROM數據地址 void setup() { Serial.begin(9600); Serial.println(""); Serial.println("Start write"); EEPROM.begin(100); for(addr = 0; addr<100; addr++) { int data = addr; EEPROM.write(addr, addr); //寫數據 } EEPROM.end(); //保存更改的數據 Serial.println("End write"); } void loop() { }
/* * 功能描述:該代碼從EEPROM讀取100字節數據 */ #include <EEPROM.h> int addr = 0; void setup() { Serial.begin(9600); Serial.println(""); Serial.println("Start read"); EEPROM.begin(100); for(addr = 0; addr<100; addr++) { int data = EEPROM.read(addr); //讀數據 Serial.print(data); Serial.print(" "); delay(2); } //釋放內存 EEPROM.end(); Serial.println("End read"); } void loop() { }
/* EEPROM Clear Sets all of the bytes of the EEPROM to 0. This example code is in the public domain. */ #include <EEPROM.h> void setup() { EEPROM.begin(100); // write a 0 to all 512 bytes of the EEPROM for (int i = 0; i < 100; i++) { EEPROM.write(i, 0); } //釋放內存 EEPROM.end(); } void loop() { }
在沒有應用結構體以前,不論是寫入仍是讀取操做,咱們都須要記住具體的存儲位置。特別是當配置數據愈來愈多的時候或者別人維護的時候,很是容易出錯。那麼有沒有辦法優雅地解決這種問題呢?固然有,那就是結構體的妙用,咱們不須要關注具體的位置,只須要關注數據自己。看如下代碼:
/* * 功能描述:eeprom結構體操做 */ #include <EEPROM.h> #define DEFAULT_STASSID "danpianjicainiao" #define DEFAULT_STAPSW "boge" struct config_type { char stassid[32]; char stapsw[64]; }; config_type config; /* * 保存參數到EEPROM */ void saveConfig() { Serial.println("Save config!"); Serial.print("stassid:"); Serial.println(config.stassid); Serial.print("stapsw:"); Serial.println(config.stapsw); EEPROM.begin(1024); uint8_t *p = (uint8_t*)(&config); for (int i = 0; i < sizeof(config); i++) { EEPROM.write(i, *(p + i)); } EEPROM.commit(); } /* * 從EEPROM加載參數 */ void loadConfig() { EEPROM.begin(1024); uint8_t *p = (uint8_t*)(&config); for (int i = 0; i < sizeof(config); i++) { *(p + i) = EEPROM.read(i); } EEPROM.commit(); Serial.println("-----Read config-----"); Serial.print("stassid:"); Serial.println(config.stassid); Serial.print("stapsw:"); Serial.println(config.stapsw); } /* *初始化 */ void setup() { ESP.wdtEnable(5000); strcpy(config.stassid, DEFAULT_STASSID); strcpy(config.stapsw, DEFAULT_STAPSW); saveConfig(); } /* *主循環 */ void loop() { ESP.wdtFeed(); loadConfig(); }
結構體與EEPROM的結合使用,使咱們脫離了存儲位置的限制,就算後期須要加多一個配置,咱們只須要在結構體上加上相應的字段,徹底不用改動其餘代碼。
這一章,講解了ESP8266 EEPROM的底層設計原理,講述了內存和flash之間的關係,也講解了方法使用,雖然簡單,可是對於底層的認知,會讓咱們優化代碼性能更加便捷。