Arduino: memoria SPI flash esterna veloce

Spread the love

Ed eccoci di nuovo a parlare di sistemi di storage, abbiamo già spiegato la gestione delle SD (potete dare un’occhiata alla gestione delle schede SD su “Come usare la scheda SD con esp8266 e Arduino”), ora andremo a dare un’occhiata ad un storage alternativo come le SPI Flash esterne, simile alle EEPROM ma con dimensioni maggiori. SD rimane sicuramente la scelta migliore per dimensioni e compatibilità, ma queste caratteristiche le paghiamo con una buona quantità di energia; la SPI Flash hanno una capacità inferiore ma è piccola, veloce e ha un consumo energetico molto basso.

Arduino UNO external SPI Flash storage
Arduino UNO external SPI Flash storage

Per dispositivi come Arduino UNO possiamo utilizzare la SPI Flash con una libreria semplice e molto leggera, ma puoi comunque gestire una buona quantità di memoria (da 256Kb a 64Mb), ed è sufficiente per molti progetti, è possibile utilizzare un filesystem completo, ma lo sconsiglio con dispositivi a risorse ridotte, vedremo come utilizzare un filesystem con dispositivi come Arduino SAMD o ESP.

Oggi vedremo le memorie SPI Flash (NOR Flash). Sono un unico chip che può essere gestito tramite SPI e hanno accesso ad alta velocità e basso consumo energetico.

La memoria flash è un supporto di memoria elettronico non volatile per computer che può essere cancellato e riprogrammato elettricamente. I due tipi principali di memoria flash, NOR flash e NAND flash, prendono il nome dalle porte logiche NOR e NAND. Le flash NAND e le flash NOR utilizzano lo stesso design della cella, costituito da MOSFET a gate flottante. Differiscono a livello di circuito: nelle flash NAND, la relazione tra la linea di bit e le linee di word assomiglia a una porta NAND; nelle flash NOR, assomiglia a un gate NOR; questo dipende dal fatto che lo stato della linea di bit o delle linee di word sia high o low.

La memoria flash, un tipo di memoria a gate flottante, è stata inventata a Toshiba nel 1980 e si basa sulla tecnologia EEPROM. Toshiba ha iniziato a commercializzare memorie flash nel 1987. Le EPROM dovevano essere cancellate completamente prima di poter essere riscritte. Le memorie flash NAND, tuttavia, possono essere cancellate, scritte e letta in blocchi (o pagine), che generalmente sono molto più piccoli dell’intero dispositivo. Le memorie flash NOR consentono di scrivere una singola parola macchina in una posizione cancellata o di leggerla in modo indipendente. I dispositivi di memoria flash sono in genere costituito da uno o più chip di memoria flash (ciascuno contenente molte celle di memoria flash), insieme a un chip controller di memoria flash separato.

Wikipedia

Pinout delle memorie SPI Flash

Ci sono SMD e IC discreti gestiti dal protocollo SPI.

Il pinout è uguale per la maggior parte delle SPI Flash (Winbond, Fujitsu ecc. ecc.), ed è lo stesso anche per SMD e componenti discreti.

SPI Flash Discrete PDIP pinout
SPI Flash Discrete PDIP pinout
SPI Flash SMD SOIC DIP8 pinout
SPI Flash SMD SOIC DIP8 pinout

Qui un set di flash SPI con dimensioni diverse w25q16 SMD 2Mb - w25q16 Discrete 2Mb - w25q32 SMD 4Mb - w25q32 Discrete 4Mb - w25q64 SMD 8Mb - w25q64 Discrete 8Mb - w25q128 SMD 16Mb - w25q128 Discrete 16Mb W25Q32 W25Q64 w25q128 module 4Mb 8Mb 16Mb

Schema elettrico con Arduino

Il primo problema con la connessione, è che Arduino UNO ha una logica 5v, ma le SPI Flash hanno una logica a 3.3v, quindi il modo più veloce per connettersi (ma non sempre il migliore) è usare un partitore di tensione, fare riferimento a questo articolo “Partitore di tensione (voltage divider): calcolatore e applicazioni”, un’altra soluzione è utilizzare un convertitore di livello logico, un semplice dispositivo che converte la logica da una tensione a un’altra e viceversa.

Qui i convertitori che utilizzo Aliexpress

The connection schema becomes like so:

ArduinoSPI Flash
10/CS Pulled UP if not standard CS;
Voltage divider.
11DI (IO1)Voltage divider
12DI (IO0)
13CLKVoltage divider
3.3v/WP
3.3v/Hold
GNDGND
3.3vVCC
Arduino UNO connection DIP8 SPI Flash breadboard w25q80
Arduino UNO connection DIP8 SPI Flash breadboard w25q80

Aggiungo un condensatore da 0,1μF e funziona bene, ma il valore standard in questa situazione è 0,01μF

Arduino UNO connection DIP8 SPIFlash schema 2 w25q32
Arduino UNO connection DIP8 SPIFlash schema 2 w25q32

Funziona bene, ma se si desidera utilizzare la modalità Dual SPI, è necessario utilizzare MISO anche per leggere i dati e questa connessione non è bidirezionale, quindi è necessario un convertitore di livello logico bidirezionale.

Libreria

La libreria selezionata per questi test è SPIMemory. Funziona bene e con poche risorse.

Puoi scaricarlo anche direttamente dall’IDE di Arduino.

SPIMemory library from Arduino IDE library manager
SPIMemory library from Arduino IDE library manager

Risoluzione dei problemi

È improbabile che la tua memoria non venga riconosciuta, ma se ciò accade, è probabilmente un problema di configurazione del canale SPI. L’insieme dei tipi di memoria supportati è molto ampio:

  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};


E se hai qualche problema con la tua flash, probabilmente è un problema di configurazione SPI.

Ad esempio, sebbene utilizzi un canale SPI standard e utilizzerai anche il costruttore standard di SPIMemory in questo modo:

SPIFlash flash;

è meglio se specifichi la configurazione in questo modo, dove SS è il selettore CS e SPI è il canale SPI:

SPIFlash flash(SS, &SPI);

se hai già problemi prova a configurare la velocità del canale SPI perché tutte le librerie cercano di trovare le migliori prestazioni per lo SPI usando le frequenze della CPU, ma in alcuni casi non può funzionare, quindi riduci il clock SPI con questo comando.

  flash.setClock(12000000); // uncomment here for Arduino SAMD boards

Comandi

I comandi sono molto semplici e possono essere usati per gestire molte situazioni. Naturalmente, l’utilizzo della ram è molto basso e, se si presta attenzione, può essere semplice gestirlo su dispositivi come Arduino UNO con memoria molto bassa.

  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);

Devi prestare attenzione a questi comandi:

uint32_t getCapacity(void): ottieni la capacità del Chip, questa libreria offre supporto per molti IC e probabilmente quando la provi, trova per te tutte le specifiche, in caso contrario puoi passare la dimensione del dispositivo al comando begin(capacity).

uint32_t getAddress(uint16_t size): con questo comando puoi ottenere un indirizzo libero che può memorizzare un dato uint16_t size.

comandi di scrutture: ci sono un sacco di comandi per vari tipi di dati, è abbastanza intuitivo e in tutti è necessario specificare un indirizzo iniziale che verrà utilizzato come punto iniziale per scrivere i dati.

comandi di lettura: come comandi di scrittura, ci sono comandi di lettura che possono essere utilizzati per rileggere i dati contigui.

template bool writeAnything(uint32_t _addr, const T& data, bool errorCheck = true): più interessante è il writeAnithing comando che serve per memorizzare una struttura complessa, ricorda, la struttura deve essere creata con un valore di dimensione statico, senza dati come String o simili .

template bool readAnything(uint32_t _addr, T& data, bool fastRead = false): quando scrivi una struttura, devi rileggerla, e questo comando lo fa, per tutti i comandi di lettura puoi impostare fastRead su true, ma non tutte le SPI Flash lo supportano, controlla il datasheet.

erase commands: puoi cancellare varie dimensioni e porzioni del chip, puoi vedere l’elenco dei comandi.

bool eraseChip(void): esiste anche uno speciale comando di cancellazione che formatta tutto il chip allo stato iniziale.

bool suspendProg(void), bool resumeProg(void): puoi sospendere i comandi di cancellazione e inizi subito a leggere.

bool powerDown(void), bool powerUp(void): mette il dispositivo in stato di basso consumo. Buono quando si usa un’alimentazione a batteria. Nel powerDown()chip risponderà solo a powerUp().

Esempi

Arduino UNO external SPI Flash memory on breadboard
Arduino UNO external SPI Flash memory on a breadboard

La libreria offre una vasta serie di esempi qui, ti mostrerò l’utilizzo di base.

Ecco un semplice esempio che memorizza la prima stringa nell’indirizzo iniziale 0, e la rileggo, quindi chiedo il metodo getAddress per la prima posizione contigua disponibile in cui posso memorizzare un’altra stringa, la salvo e la rileggo.

Per Arduino MKR, devi impostare il costruttore in questo modo:

SPIFlash flash(SS, &SPI);

Ottenere le informazioni della nostra SPI Flash

/*
 *  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>
 *
 *  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() {

}

Qui il risultato della Serial:

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

Salvare e rileggere una struttura JSON

Ecco un esempio più realistico, invece di salvare un testo come una stringa, salveremo una struttura JSON come una stringa.

/*
 *  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>
 *
 *  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() {

}

Ecco il risultato della console.

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

Salvare e rileggere una struttura

Naturalmente, puoi utilizzare una struttura JSON per salvare dati complessi, ma se vuoi ottenere le migliori prestazioni e un migliore utilizzo dello spazio devi utilizzare una struttura del genere.

/*
 *  Manage external SPI Flash with Arduino
 *  Write and read a structure
 *
 *  with library SPIMemory
 *
 *  by Mischianti Renzo <https://mischianti.org>
 *
 *  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() {

}

Ecco il risultato seriale (con diagnostica attiva).

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

Il file system FAT su SPI Flash

È possibile utilizzare anche un file system Fat, ma sconsiglio questo approccio perché le risorse di Arduino UNO e Mega sono troppo basse per utilizzare quelle risorse, ma se vuoi provare, puoi leggere l’articolo su SPI Flash per Arduino SAMD o esp8266 e esp32.

Grazie

  1. Arduino: memoria SPI flash esterna veloce
  2. Arduino MKR SAMD: filesystem FAT su memoria SPI flash esterna
  3. esp32 e esp8266: file system FAT su memoria SPI flash esterna
  4. STM32: memoria SPI flash FAT FS

Spread the love

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *