Arduino 快速外部 SPI 快閃記憶體
by Richard · 2024 年 3 月 30 日
作者: 倫佐·米斯基安蒂 ·出版 · 更新
Arduino: fast external SPI Flash memory
今天我們來看看SPI快閃記憶體(NOR Flash)。它們是一個單晶片,可以透過SPI進行管理,具有高速存取和低功耗的特性。
快閃記憶體 是一種電子非揮發性電腦記憶體儲存介質,可以電擦除和重新編程。快閃記憶體的兩種主要類型:NOR 快閃記憶體和 NAND 快閃記憶體,以 NOR 和 NAND 邏輯閘命名。 NAND 快閃記憶體和 NOR 快閃記憶體採用相同的單元設計,由浮柵 MOSFET 組成。它們在電路層面上有所不同:在NAND快閃記憶體中,位元線和字線之間的關係類似於NAND閘;而在NAND快閃記憶體中,位元線和字線之間的關係類似於NAND閘。在NOR快閃記憶體中,它類似於或非門;這取決於位線或字線的狀態是拉高還是拉低。
快閃記憶體是一種浮柵記憶體,由東芝於 1980 年發明,基於 EEPROM 技術。東芝於 1987 年開始銷售快閃記憶體。EPROM 必須完全擦除才能重寫。然而,NAND 快閃記憶體可以按區塊(或頁)進行擦除、寫入和讀取,區塊(或頁)通常比整個裝置小得多。 NOR 快閃記憶體允許將單一機器字寫入已擦除位置或獨立讀取。快閃記憶體元件通常由一個或多個快閃記憶體晶片(每個晶片包含許多快閃記憶體單元)以及單獨的快閃記憶體控制器晶片組成。
維基百科
SPI 快閃記憶體引腳排列
有SMD和透過SPI協議管理的分立IC。
大多數 SPI Flash(華邦、富士通等)的接腳排列都是相同的,SMD 和分立元件也是相同的。
SPI 快閃記憶體離散 PDIP 引腳排列
這裡有一組不同大小的 SPI 快閃記憶體 w25q16 SMD 2Mb – w25q16 Discrete 2Mb – w25q32 SMD 4Mb – w25q32 Discrete 4Mb – w25q64 SMD 8Mb – w2564 Discrete 4Mb – w25q64 SMD 8Mb – w2564 Discrete 8Mb – w25q 5Q32 W 25Q64 w25q128 模組 4Mb 8Mb 16Mb
Arduino接線圖
連接的第一個問題是Arduino UNO有5v邏輯,但SPI Flash有3.3v邏輯,所以最快的連接方式(但不是最好的)是使用分壓器,參考這篇文章「分壓器:計算器和應用程式“,另一種解決方案是使用邏輯電平轉換器,這是一種將邏輯從電壓轉換為另一種電壓的簡單設備,反之亦然。
這裡是邏輯電平轉換器 Aliexpress
連接模式變成這樣:
Arduino | SPI快閃記憶體 | |
---|---|---|
10 | /CS | 如果不是標準 CS,則拉起; 分壓器。 |
11 | DI(輸入輸出1) | 分壓器 |
12 | DI(IO 0) | |
13 | 時鐘 | 分壓器 |
3.3V | /WP | |
3.3V | /抓住 | |
接地 | 接地 | |
3.3V | 電壓控制電路 |
我加了一個0.1μF的電容,效果很好,但這種情況下的標準值是0.01μF
它工作得很好,但如果你想使用雙 SPI 模式,你還需要使用 MISO 來讀取數據,而且這種連接不是雙向的,所以你需要一個雙向邏輯電平轉換器。
圖書館
為這些測試選擇的函式庫是 SPIMemory。它運作良好且資源較少。
您也可以直接在 Arduino IDE 中下載它。
故障排除
您的記憶體不太可能無法被識別,但如果發生這種情況,則可能是 SPI 通道配置問題。支援的記憶體類型集非常廣泛:
1 2 3 4 5 6 7 8 9 10 | const uint8_t _capID[ 18 ] = { 0x10 , 0x11 , 0x12 , 0x13 , 0x14 , 0x15 , 0x16 , 0x17 , 0x18 , 0x19 , 0x41 , 0x42 , 0x43 , 0x4B , 0x00 , 0x01 , 0x13 , 0x37 }; const uint32_t _memSize[ 18 ] = {KB( 64 ), KB( 128 ), KB( 256 ), KB( 512 ), MB( 1 ), MB( 2 ), MB( 4 ), MB( 8 ), MB( 16 ), MB( 32 ), MB( 2 ), MB( 4 ), MB( 8 ), MB( 8 ), KB( 256 ), KB( 512 ), MB( 4 ), KB( 512 )}; // To understand the _memSize definitions check defines.h const uint8_t _supportedManID[ 9 ] = {WINBOND_MANID, MICROCHIP_MANID, CYPRESS_MANID, ADESTO_MANID, MICRON_MANID, ON_MANID, GIGA_MANID, AMIC_MANID, MACRONIX_MANID}; const uint8_t _altChipEraseReq[ 3 ] = {A25L512, M25P40, SST26}; |
如果您的快閃記憶體有問題,可能是 SPI 設定問題。
例如,雖然您使用標準 SPI 通道,但您也會使用 SPIMemory 的標準建構函數,如下所示:
1 | SPIFlash flash; |
最好像這樣指定配置,其中 SS 是 CS 選擇器,SPI 是 SPI 通道:
1 | SPIFlash flash(SS, & SPI); |
如果您已經遇到問題,請嘗試設定 SPI 通道的速度,因為所有程式庫都嘗試使用 CPU 頻率找到 SPI 的最佳效能,但在某些情況下可能不起作用,因此請使用此命令降低 SPI 時脈。
1 | flash.setClock( 12000000 ); // uncomment here for Arduino SAMD boards |
命令
這些命令非常簡單,可以用來管理很多情況。當然,RAM 使用率非常低,如果您注意的話,可以在 Arduino UNO 等記憶體非常低的裝置上輕鬆管理它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 號 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | bool sfdpPresent( void ); uint8_t error(bool verbosity = false ); uint16_t getManID( void ); uint32_t getJEDECID( void ); uint64_t getUniqueID( void ); uint32_t getAddress(uint16_t size); uint16_t sizeofStr(String & inputStr); uint32_t getCapacity( void ); uint32_t getMaxPage( void ); float functionRunTime( void ); //-------------------------------- Write / Read Bytes ---------------------------------// bool writeByte(uint32_t _addr, uint8_t data, bool errorCheck = true ); uint8_t readByte(uint32_t _addr, bool fastRead = false ); //----------------------------- Write / Read Byte Arrays ------------------------------// bool writeByteArray(uint32_t _addr, uint8_t * data_buffer, size_t bufferSize, bool errorCheck = true ); bool readByteArray(uint32_t _addr, uint8_t * data_buffer, size_t bufferSize, bool fastRead = false ); //-------------------------------- Write / Read Chars ---------------------------------// bool writeChar(uint32_t _addr, int8_t data, bool errorCheck = true ); int8_t readChar(uint32_t _addr, bool fastRead = false ); //------------------------------ Write / Read Char Arrays -----------------------------// bool writeCharArray(uint32_t _addr, char * data_buffer, size_t bufferSize, bool errorCheck = true ); bool readCharArray(uint32_t _addr, char * data_buffer, size_t buffer_size, bool fastRead = false ); //-------------------------------- Write / Read Shorts --------------------------------// bool writeShort(uint32_t _addr, int16_t data, bool errorCheck = true ); int16_t readShort(uint32_t _addr, bool fastRead = false ); //-------------------------------- Write / Read Words ---------------------------------// bool writeWord(uint32_t _addr, uint16_t data, bool errorCheck = true ); uint16_t readWord(uint32_t _addr, bool fastRead = false ); //-------------------------------- Write / Read Longs ---------------------------------// bool writeLong(uint32_t _addr, int32_t data, bool errorCheck = true ); int32_t readLong(uint32_t _addr, bool fastRead = false ); //--------------------------- Write / Read Unsigned Longs -----------------------------// bool writeULong(uint32_t _addr, uint32_t data, bool errorCheck = true ); uint32_t readULong(uint32_t _addr, bool fastRead = false ); //-------------------------------- Write / Read Floats --------------------------------// bool writeFloat(uint32_t _addr, float data, bool errorCheck = true ); float readFloat(uint32_t _addr, bool fastRead = false ); //-------------------------------- Write / Read Strings -------------------------------// bool writeStr(uint32_t _addr, String & data, bool errorCheck = true ); bool readStr(uint32_t _addr, String & data, bool fastRead = false ); //------------------------------- Write / Read Anything -------------------------------// template <class T> bool writeAnything(uint32_t _addr, const T & data, bool errorCheck = true ); template <class T> bool readAnything(uint32_t _addr, T & data, bool fastRead = false ); //-------------------------------- Erase functions ------------------------------------// bool eraseSection(uint32_t _addr, uint32_t _sz); bool eraseSector(uint32_t _addr); bool eraseBlock32K(uint32_t _addr); bool eraseBlock64K(uint32_t _addr); bool eraseChip( void ); //-------------------------------- Power functions ------------------------------------// bool suspendProg( void ); bool resumeProg( void ); bool powerDown( void ); bool powerUp( void ); |
您必須注意這些命令:
uint32_t getCapacity(void)
:取得晶片的容量,這個庫提供了對很多IC的支持,可能當你嘗試它時,為你找到所有規格,如果沒有,你可以將設備的大小傳遞給命令begin(capacity)
。
uint32_t getAddress(uint16_t size)
:使用此命令可以獲得一個可以儲存uint16_t size
資料的空閒位址。
write commands:
對於各種類型的資料有很多命令,非常直觀,總而言之,您必須指定一個起始位址,該起始位址將用作寫入資料的起始點。
read command
:與寫入命令一樣,還有讀取命令可用於重新讀取連續資料。
template bool writeAnything(uint32_t _addr, const T& data, bool errorCheck = true)
:更有趣的是writeAnithing
用於儲存複雜結構的命令,記住,該結構必須使用靜態大小值創建,不能使用 String 或類似的資料。
template bool readAnything(uint32_t _addr, T& data, bool fastRead = false)
:當你寫一個結構體時,你必須重新讀取It,而這個命令做了It,對於所有的讀取命令,你可以設定fastRead
為true
,但不是所有的SPI Flash都支援It,查看datasheet。
erase commands:
你可以擦除晶片的各種尺寸和部分,你可以看到命令列表。
bool eraseChip(void)
:還有一個特殊的擦除指令,可以將所有晶片格式化為初始狀態。
bool suspendProg(void), bool resumeProg(void)
:您可以暫停擦除命令,並立即開始讀取。
bool powerDown(void), bool powerUp(void)
:將設備置於低功耗狀態。使用電池供電時效果很好。在powerDown()
晶片中只會做出反應powerUp()
。
例子
該庫在這裡提供了大量範例,我將向您展示基本用法。
這是一個簡單的範例,將第一個字串儲存在初始位址 0 中,然後我重新讀取它,而不是詢問該方法getAddress
是否可以儲存另一個字串的第一個可用連續位置,然後保存它並重新讀取。
對於Arduino MKR,您必須像這樣設定建構函數:
SPIFlash flash(SS, & SPI); |
取得SPI Flash信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 號 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | /* * Manage external SPI Flash with Arduino * Write and read a string, * find first address available * and write and read another string * * with library SPIMemory * * by Mischianti Renzo <https://mischianti.org> * * * SPIFlash connected via SPI standard * */ #include <SPIMemory.h> //SPIFlash flash; // If you don't specify the library use standard SPI connection SPIFlash flash; void setup () { Serial.begin ( 115200 ); while ( ! Serial) ; // Wait for Serial monitor to open delay ( 100 ); // flash.setClock(12000000); // uncomment here for Arduino SAMD boards flash.begin(); // If SPIMemory isn't recognized you can specify the size of memory // flash.eraseChip(); Serial.print (F( "Flash size: " )); Serial.print (( long )(flash.getCapacity() / 1000 )); Serial.println (F( "Kb" )); unsigned long strAddr = 0 ; unsigned long strAddrSecondString = 0 ; Serial.println (); String inputString = "I'm going to write this string on IC" ; flash.writeStr(strAddr, inputString); Serial.print (F( "Written string: " )); Serial.println (inputString); Serial.print (F( "To address: " )); Serial.println (strAddr); String outputString = "" ; if (flash.readStr(strAddr, outputString)) { Serial.print (F( "Read string: " )); Serial.println (outputString); Serial.print (F( "From address: " )); Serial.println (strAddr); } Serial.println (); String secondInputString = "I'm going to write this second string on IC" ; Serial.print (F( "Check first free sector: " )); strAddrSecondString = flash.getAddress(secondInputString.length()); Serial.println (strAddrSecondString); Serial.println (); flash.writeStr(strAddrSecondString, secondInputString); Serial.print (F( "Written string: " )); Serial.println (secondInputString); Serial.print (F( "To address: " )); Serial.println (strAddrSecondString); outputString = "" ; if (flash.readStr(strAddrSecondString, outputString)) { Serial.print (F( "Read string: " )); Serial.println (outputString); Serial.print (F( "From address: " )); Serial.println (strAddrSecondString); } while ( ! flash.eraseSector(strAddr)); while ( ! flash.eraseSector(strAddrSecondString)); } void loop () { } |
這是串行結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 | Flash size: 8388Kb Written string: I'm going to write this string on IC To address: 0 Read string: I'm going to write this string on IC From address: 0 Check first free sector: 43 Written string: I'm going to write this second string on IC To address: 43 Read string: I'm going to write this second string on IC From address: 43 |
保存和讀取 JSON 結構
這是一個更實際的例子,我們不會像字串一樣保存文本,而是像字串一樣保存 JSON 結構。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 號 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | /* * Manage external SPI Flash with Arduino * Write and read a JSON structure like a String, * find first address available * and write and read another JSON structure * * with library SPIMemory * * by Mischianti Renzo <https://mischianti.org> * * * SPIFlash connected via SPI standard * */ #include <SPIMemory.h> #include <ArduinoJson.h> //SPIFlash flash; // If you don't specify the library use standard SPI connection SPIFlash flash; void setup () { Serial.begin ( 115200 ); while ( ! Serial) ; // Wait for Serial monitor to open delay ( 100 ); // flash.setClock(12000000); // uncomment here for Arduino SAMD boards flash.begin(); // If SPIMemory isn't recognized you can specify the size of memory // flash.eraseChip(); Serial.print (F( "Flash size: " )); Serial.print (( long )(flash.getCapacity() / 1000 )); Serial.println (F( "Kb" )); unsigned long strAddr = 0 ; unsigned long strAddrSecondString = 0 ; Serial.println (); // Allocate a temporary JsonDocument // Don't forget to change the capacity to match your requirements. // Use arduinojson.org/v6/assistant to compute the capacity. // StaticJsonDocument<512> doc; // You can use DynamicJsonDocument as well Serial.println (F( "Generate JSON file!" )); DynamicJsonDocument doc( 512 ); // Set the values in the document doc[ "energyLifetime" ] = 21698620 ; doc[ "energyYearly" ] = 1363005 ; Serial.print (F( "Put data in a buffer.. " )); // Serialize JSON to file String buf; if (serializeJson(doc, buf) = = 0 ) { Serial.println (F( "failed to write buffer" )); } if (flash.writeStr(strAddr, buf)){ Serial.print (F( "OK, writed on address " )); Serial.println (strAddr); } else { Serial.println (F( "KO" )); } String outputString = "" ; if (flash.readStr(strAddr, outputString)) { Serial.print (F( "Read json: " )); Serial.println (outputString); Serial.print (F( "From address: " )); Serial.println (strAddr); } Serial.println (F( "Generate JSON file!" )); DynamicJsonDocument doc2( 512 ); // Set the values in the document doc2[ "energyLifetime" ] = 222 ; doc2[ "energyYearly" ] = 333 ; Serial.println (); Serial.print (F( "Check first free sector: " )); strAddrSecondString = flash.getAddress(doc2.size()); Serial.println (strAddrSecondString); Serial.println (); Serial.print (F( "Stream data in flash memory!" )); Serial.print (F( "Put data in a buffer.." )); // Serialize JSON to file String buf2; if (serializeJson(doc2, buf2) = = 0 ) { Serial.println (F( "failed to write buffer" )); } // Print test file if (flash.writeStr(strAddrSecondString, buf2)){ Serial.print (F( "OK, writed on address " )); Serial.println (strAddrSecondString); } else { Serial.println (F( "KO" )); } String outputString2 = "" ; if (flash.readStr(strAddrSecondString, outputString2)) { Serial.print (F( "Read data: " )); Serial.println (outputString2); Serial.print (F( "From address: " )); Serial.println (strAddrSecondString); } while ( ! flash.eraseSector(strAddr)); while ( ! flash.eraseSector(strAddrSecondString)); } void loop () { } |
這是控制台結果。
1 2 3 4 5 6 7 8 9 10 11 12 13 | Flash size: 8388Kb Generate JSON file! Put data in a buffer.. OK, writed on address 0 Read json: {"energyLifetime":21698620,"energyYearly":1363005} From address: 0 Generate JSON file! Check first free sector: 56 Stream data in flash memory!Put data in a buffer..OK, writed on address 56 Read data: {"energyLifetime":222,"energyYearly":333} From address: 56 |
保存和讀取結構
當然,您可以使用 JSON 結構來保存複雜的數據,但如果您想獲得最佳性能和更好的空間利用率,則必須使用這樣的結構。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 號 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | /* * Manage external SPI Flash with Arduino * Write and read a structure * * with library SPIMemory * * by Mischianti Renzo <https://mischianti.org> * * * SPIFlash connected via SPI standard * */ #include <SPIMemory.h> // I'm going to use the structure use in standard example struct ConfigurationIn { float lux = 3.24 ; float vOut = 4.45 ; // Voltage ouput from potential divider to Analog input float RLDR = 1.234 ; // Resistance calculation of potential divider with LDR bool light = true ; uint8_t adc = 45 ; uint8_t arr[ 8 ] = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 }; struct MISC { byte tempHigh = 30 ; byte tempLow = 20 ; bool parkingMode = false ; bool allowDataToBeSent = false ; } misc; struct NETWORK { char ssid[ 5 ] = "ssid" ; char pwd[ 4 ] = "pwd" ; char userid[ 7 ] = "userid" ; } network; struct CHARGING_INFO { byte interval = 5 ; byte highChargingDefault = 80 ; } charging; }; ConfigurationIn configurationIn; struct ConfigurationOut { float lux; float vOut; // Voltage ouput from potential divider to Analog input float RLDR; // Resistance calculation of potential divider with LDR bool light; uint8_t adc; uint8_t arr[ 8 ]; struct MISC { byte tempHigh; byte tempLow; bool parkingMode; bool allowDataToBeSent; } misc; struct NETWORK { char ssid[ 5 ]; char pwd[ 4 ]; char userid[ 7 ]; } network; struct CHARGING_INFO { byte interval; byte highChargingDefault; } charging; }; ConfigurationOut configurationOut; //SPIFlash flash; // If you don't specify the library use standard SPI connection SPIFlash flash; void setup () { Serial.begin ( 115200 ); while ( ! Serial) ; // Wait for Serial monitor to open delay ( 100 ); // flash.setClock(12000000); // uncomment here for Arduino SAMD boards flash.begin(); // If SPIMemory isn't recognized you can specify the size of memory // flash.eraseChip(); Serial.print (F( "Flash size: " )); Serial.print (( long )(flash.getCapacity() / 1000 )); Serial.println (F( "Kb" )); unsigned long strAddr = 0 ; Serial.println (); Serial.print (F( "Start writing structure " )); if (flash.writeAnything(strAddr, configurationIn, true )){ Serial.println ( "OK" ); } else { Serial.println ( "KO" ); } Serial.println (); if (flash.readAnything(strAddr, configurationOut)) { Serial.print (F( "Read lux on configuration loaded from Flash: " )); Serial.println (configurationOut.lux); } else { Serial.println (F( "Read not work!" )); } Serial.println (); while ( ! flash.eraseSector(strAddr)); } void loop () { } |
這是串行結果(診斷處於活動狀態)。
1 2 3 4 5 6 7 8 9 | Chip Diagnostics initiated. No Chip size defined by user. Checking library support. Chip identified. This chip is fully supported by the library. Flash size: 8388Kb Start writing structure OK Read lux on configuration loaded from Flash: 3.24 |
SPI Flash 上的 fat 檔案系統
也可以使用 Fat 檔案系統,但我不鼓勵這種方法,因為 Arduino UNO 和 Mega 的資源太低,無法使用這些資源,但如果您想嘗試,您可以閱讀有關 Arduino SAMD 或 esp8266 的 SPI Flash 的文章和esp32。