STM32: add SPI flash memory with FAT FS

Spread the love

Early SMT32 prototype boards did not have built-in SPI flash, but the latest ones, like the WeAct STM32F4 board, have a footprint to add it. And I find this option very useful, the only problem is that the Arduino Core doesn’t have a native library, so we’re going to use the Adafruit one.

STM32 SPI Flash memory storage
STM32 SPI Flash memory storage

We already had explained how to manage SD (you can look at SD management on “How to use SD card with stm32 and SdFat library“), and now we’d like to look at alternative storage like external SPI Flash, similar to the EEPROM but with the biggest size. The SPI Flash has a smaller capacity but is small, fast, and has very low power consumption. SD It’s surely the best choice for size and compatibility, but we pay for these features with a good quantity of energy.

Here my selection of STM32 STM32F103C8T6 STM32F401 STM32F411 ST-Link v2 ST-Link v2 official

Today, we will see the SPI Flash memory (NOR Flash); they are a single chip that can be managed via SPI and have high-speed access and low power consumption.

Flash memory is an electronic non-volatile computer memory storage medium that can be electrically erased and reprogrammed. The two main types of flash memory, NOR flash and NAND flash, are named for the NOR and NAND logic gates. NAND flash and NOR flash use the same cell design, consisting of floating gate MOSFETs. They differ at the circuit level: in NAND flash, the relationship between the bit line and the word lines resembles a NAND gate; in NOR flash, it resembles a NOR gate; this depends on whether the state of the bit line or word lines is pulled high or low.

Flash memory, a type of floating-gate memory, was invented at Toshiba in 1980 and is based on EEPROM technology. Toshiba began marketing flash memory in 1987. EPROMs had to be erased completely before they could be rewritten. NAND flash memory, however, may be erased, written, and read in blocks (or pages), which generally are much smaller than the entire device. NOR flash memory allows a single machine word to be written – to an erased location – or read independently. A flash memory device typically consists of one or more flash memory chips (each holding many flash memory cells), along with a separate flash memory controller chip.

Wikipedia

Flash memories

There are SMD and Discrete IC managed by SPI protocol.

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

Here a set of SPI flash with different size 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

STM32F1 blue-pill wiring

We start with the classic STM32F1 blue-pill very common prototype board without SPI Flash footprint.

Pinout STM32 STM32F1 STM32F103 STM32F103C8 low resolution
Pinout STM32 STM32F1 STM32F103 STM32F103C8 low resolution

So you can refer to the pinout and connect pins 8, 9, 10, and 4 for CS.

STM32F1SPI Flash
PA4/CS Pulled UP if not standard CS
PA6DI (IO1)
PA7DI (IO0)
PA5CLK
3.3v/WP
3.3v/Hold
GNDGND
3.3vVCC
STM32 SPI Flash wiring on breadboard
STM32 SPI Flash wiring on breadboard

For the capacitor, I use a 0.1μF, and It works correctly, but the standard value was 0.01μF.

STM32 SPI Flash wiring schema
STM32 SPI Flash wiring schema

STM32F4 black-pill wiring

This device has a native footprint to add SPI Flash.

STM32 STM32F401 STM32F401CCU6 pinout low resolution
STM32 STM32F401 STM32F401CCU6 pinout low resolution

For this device, we are going to use the default footprint.

STM32F4SPI Flash
PA4CS
PA6DI (IO1)
PA7DI (IO0)
PA5CLK
3.3v/WP
3.3v/Hold
GNDGND
3.3vVCC

For the capacitor, I used a 0.1μF, and It works correctly, but the standard value was 0.01μF.

STM32F4 SPI Flash wiring schema
STM32F4 SPI Flash wiring schema

Here is the footprint before adding the SPI Flash. You can also see the footprint for a capacitor (C15) for the best performance, you must add a 104 0.1uF 0402 cap.

STM32 STM32F4 black pill bottom SPI Flash footprint
STM32 STM32F4 black pill bottom SPI Flash footprint

You can get more information on the relative article with the pinout and schema.

STM32 STM32F4 black pill with SPI Flash installed
STM32 STM32F4 black pill with SPI Flash installed

SPI Flash basic usage

If you don’t need a particular data structures, you can do basic and raw usage of the SPI Flash.

For additional information on this library, you can see the article “Arduino: fast external SPI Flash memory“.

For basic usage, I advise using a library named SPIMemory, which is quite simple but with good support without difficulty. You can download It via the library manager from Arduino IDE.

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

Here is a simple example that stores a JSON string in the initial address 0, and I re-read It, then I ask the method getAddress for the first available contiguous position where I can store another JSON string, and I save It and re-read.

/*
 *  Manage external SPI Flash with STM32
 *  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 <SPI.h>
#include <SPIMemory.h>
#include <ArduinoJson.h>

//SPIFlash flash; // If you don't specify the library use standard SPI connection
SPIFlash flash(SS);
void setup() {
  Serial.begin(115200);

  while (!Serial) ; // Wait for Serial monitor to open
  delay(100);

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

}

Here is the console result.

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

SPI Flash advanced use with SdFat filesystem

A more complex library (and more hungry of resources) It’s the Adafruit one, the Adafruit SPIFlash, that needs to be used with the SdFat Adafruit fork.

You can also find these libraries on library manager “Adafruit SPIFlash” and “SdFat – Adafruit Fork”.

Arduino IDE library manager Adafruit SPIFlash and SdFat fork
Arduino IDE library manager Adafruit SPIFlash and SdFat fork

Add new SPI Flash type

The set of chips is supported. It’s limited to a specified list but creates a new device. It’s simple; for example, I have a W25X80AVAIZ (I think Winbond clone), a discrete component buy for a very low price.
It has these characteristics:

  • 1Mb capacity;
  • 104Mhz of speed;
  • Winbond clone;
  • Single dual and quad SPI.

Then, I write a simple sketch to check the compatibility:

/*
 *  Retrieve basic core info of Flash SPI
 *  Add a custom device w25x80
 *  library Adafruit_SPIFlash and SdFat - AdafruitFork
 *
 *  by Mischianti Renzo <https://mischianti.org>
 *
 *  https://mischianti.org/
 *
 *	SPIFlash connected via SPI standard check wiring on the article
 *
 */

#include "SdFat.h"
#include "Adafruit_SPIFlash.h"

Adafruit_FlashTransport_SPI flashTransport(SS, SPI); // Set CS and SPI interface
Adafruit_SPIFlash flash(&flashTransport);

void setup()
{
  Serial.begin(115200);
  while ( !Serial ) delay(100);   // wait for native usb

  if (flash.begin()) {
	  Serial.println(F("Device finded and supported!"));
  } else {
	  Serial.println(F("Problem to discover and configure device, check wiring also!"));
  }
  // Set 4Mhz SPI speed
  flashTransport.setClockSpeed(4000000, 4000000); // added to prevent speed problem

  Serial.println();

  Serial.println("Adafruit Serial Flash get basic info: ");
  Serial.print("JEDEC ID (FFFFFF for unknown): "); Serial.println(flash.getJEDECID(), HEX);
  Serial.print("Flash size: "); Serial.println(flash.size());

  Serial.println();Serial.println();

  uint8_t jedec_ids[4];
  flashTransport.readCommand(SFLASH_CMD_READ_JEDEC_ID, jedec_ids, 4);

  // For simplicity with commonly used device, we only check for continuation
  // code at 2nd byte (e.g Fujitsu FRAM devices)
  if (jedec_ids[1] == 0x7F) {
    // Shift and skip continuation code in 2nd byte
    jedec_ids[1] = jedec_ids[2];
    jedec_ids[2] = jedec_ids[3];
  }

  Serial.println("Retrieve JDEC_ID");

  Serial.print("Manufacturer ID: 0x");
  Serial.println(jedec_ids[0], HEX);

  Serial.print("Memory Type: 0x");
  Serial.println(jedec_ids[1], HEX);

  Serial.print("Capacity: 0x");
  Serial.println(jedec_ids[2], HEX);
  Serial.print("Capacity DEC: ");
  Serial.println(jedec_ids[2], DEC);
}

void loop()
{
  // nothing to do
}

The serial output for my unsupported device is:

Unknown flash device 0xEF4014
Problem to discover and configure device, check wiring also!

Adafruit Serial Flash get basic info: 
JEDEC ID (FFFFFF for unknown): FFFFFF